使用和不使用-std = c99的代码会产生不同的结果(UMAC AE实施)

时间:2015-01-19 17:33:28

标签: c gcc c99 compiler-options

很长(很长)的故事简短 - 我使用Ted Krovetz的实现来计算UMACUMAC AE加密(http://www.fastcrypto.org/)。

当我使用umac.c编译我的代码(和/或-std=c99中的测试)时,计算出的UMAC完全不同于预期(和错误)。当我删除此选项时,一切都像魅力一样。

任何可能导致此问题的想法?我能做些什么来检查发生了什么以及产生不同结果的是什么?


$ gcc --version
gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2

$ uname -a
xxx 3.13.0-43-generic #72-Ubuntu SMP .. x86_64 x86_64 x86_64 GNU/Linux

我不使用任何其他选项 - 只使用和不使用-std=c99


再说几句话:

我会尝试联系Ted Krovetz并问他(这可能是一些错误或其他什么),但这不是重点。问题有点笼统,这个具体问题可以看作是例子。

我跑了valgrind - 没有特别。已再次添加-Wall-Wextra - 。听起来像UB,但valgrind并没有抱怨任何事情。

情况非常有趣,花了我很多天和头痛来理解,问题不在我的代码中(我使用此实现来实现复杂的协议),但在算法,特别是在这个选项中。所以我决定征求意见。

Can code that is valid in both C and C++ produce different behavior when compiled in each language?根本没有关系,因为我们在这里谈的是同一种语言 这个Massive fprintf speed difference without "-std=c99"很接近,但还不够......


修改

以下是我的测试结果和我的工作(刚刚下载了源/标题,我没有更改任何内容):

$ ll
total 176K
-rw-r----- 1 kk kk  63K Jan 20 11:00 rijndael-alg-fst.c
-rw-r----- 1 kk kk 2.0K Jan 20 11:00 rijndael-alg-fst.h
-rw-r----- 1 kk kk 3.4K Jan 20 11:00 umac_ae.h
-rw-r----- 1 kk kk  76K Jan 20 11:00 umac.c
-rw-r----- 1 kk kk 4.2K Jan 20 11:00 umac.h

$ gcc -c *.c

$ gcc *.o

$ ./a.out 
AES Test :::
Digest is       : 3AD78E726C1EC02B7EBFE92B23D9EC34
Digest should be: 3AD78E726C1EC02B7EBFE92B23D9EC34

UMAC Test :::
Msg           Should be        Is
---           ---------        --
'a' *     0 : 4D61E4F5AAB959C8 4D61E4F5AAB959C8
'a' *     3 : 67C1700CA30B532D 67C1700CA30B532D
'a' *  1024 : 05CB9405EC38D9F0 05CB9405EC38D9F0
'a' * 32768 : 048C543CB72443A4 048C543CB72443A4

Verifying consistancy of single- and multiple-call interfaces.
Done.

Authenticating       44 byte messages:  6.45 cpb.
Authenticating       64 byte messages:  4.18 cpb.
Authenticating      256 byte messages:  1.63 cpb.
Authenticating      512 byte messages:  1.20 cpb.
Authenticating      552 byte messages:  1.22 cpb.
Authenticating     1024 byte messages:  1.00 cpb.
Authenticating     1500 byte messages:  1.04 cpb.
Authenticating     8192 byte messages:  0.90 cpb.
Authenticating   262144 byte messages:  0.89 cpb.

UMAC-AE Tests :::
0 bytes ('abc' * 0):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : E8D1DAC3EA21E56D
3 bytes ('abc' * 1):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : 6BEDBA31E074E2A4
48 bytes ('abc' * 16):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : A3F6069B913969DA
300 bytes ('abc' * 100):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : C5B7F3822179FC36
3000000 bytes ('abc' * 1000000):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : EE7F50FDDA60AA04
  16 bytes, 38.12 cpb
  32 bytes, 25.04 cpb
  64 bytes, 19.39 cpb
 128 bytes, 16.41 cpb
 256 bytes, 14.79 cpb
 512 bytes, 13.96 cpb
1024 bytes, 13.79 cpb
2048 bytes, 13.46 cpb
4096 bytes, 13.47 cpb










$ ll
total 176K
-rw-r----- 1 kk kk  63K Jan 20 11:00 rijndael-alg-fst.c
-rw-r----- 1 kk kk 2.0K Jan 20 11:00 rijndael-alg-fst.h
-rw-r----- 1 kk kk 3.4K Jan 20 11:00 umac_ae.h
-rw-r----- 1 kk kk  76K Jan 20 11:00 umac.c
-rw-r----- 1 kk kk 4.2K Jan 20 11:00 umac.h

$ gcc -std=c99 -c *.c 

$ gcc -std=c99 *.o

$ ./a.out 
AES Test :::
Digest is       : 3AD78E726C1EC02B7EBFE92B23D9EC34
Digest should be: 3AD78E726C1EC02B7EBFE92B23D9EC34

UMAC Test :::
Msg           Should be        Is
---           ---------        --
'a' *     0 : 4D61E4F5AAB959C8 9492DE86794C9F2B
'a' *     3 : 67C1700CA30B532D CF9505F52928360E
'a' *  1024 : 05CB9405EC38D9F0 9C48C0D4EFAFAA37
'a' * 32768 : 048C543CB72443A4 7F63C29BB54BB141

Verifying consistancy of single- and multiple-call interfaces.
Done.

Authenticating       44 byte messages:  7.91 cpb.
Authenticating       64 byte messages:  5.20 cpb.
Authenticating      256 byte messages:  3.03 cpb.
Authenticating      512 byte messages:  2.60 cpb.
Authenticating      552 byte messages:  2.71 cpb.
Authenticating     1024 byte messages:  2.41 cpb.
Authenticating     1500 byte messages:  2.43 cpb.
Authenticating     8192 byte messages:  2.27 cpb.
Authenticating   262144 byte messages:  2.23 cpb.

UMAC-AE Tests :::
0 bytes ('abc' * 0):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : 899C50FD244BBA83
3 bytes ('abc' * 1):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : 892D14F581A3A4DD
48 bytes ('abc' * 16):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : 621AB4A63383F3C5
300 bytes ('abc' * 100):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : 324BEF6489F57787
3000000 bytes ('abc' * 1000000):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : 1A25FE3714C9345A
  16 bytes, 40.80 cpb
  32 bytes, 25.87 cpb
  64 bytes, 20.50 cpb
 128 bytes, 17.72 cpb
 256 bytes, 15.93 cpb
 512 bytes, 15.33 cpb
1024 bytes, 14.88 cpb
2048 bytes, 14.71 cpb
4096 bytes, 14.48 cpb

我刚刚在另一台机器上进行了测试,它与我的相同。

3 个答案:

答案 0 :(得分:4)

  

听起来像UB,

它不是必须的。有几个已知的差异可能会导致相同的程序被解释为C99和C90。

  

但是valgrind并没有抱怨任何事情。

Valgrind甚至没有接近警告任何标准中的所有未定义行为。

首先想到的区别(但是好的编译器会发出警告)是整数常量3000000000的类型。使用32位int和64位long,C90编译器将3000000000键入为unsigned long。在C99中,unsigned long不在没有后缀的整数常量可以具有的类型列表中,因此3000000000被键入为long long(已签名)。

不看,加密代码可能有很多大的整数常量,所以这是一种可能性。

当然,在解释为C90或C99的代码中可能存在未定义的行为,然后编译器在C90和C99模式下产生不同的结果是可以理解的。我所说的只是没有必要。

答案 1 :(得分:4)

好吧,我明白了。仍然不确定如何将其修复为完美/便携,但我会继续挖掘。

长话短说 - 它似乎是特定于平台的,这就是为什么大多数人都没有这个问题的原因 问题在于确定字节顺序。


详细说明:

在比较装配输出之后,存在一些显着差异,这些差异(几乎自动地)排除了一些大常数解释和类似小问题的问题。

然后我尝试了更高级别 - 预处理器输出。

最后,所有内容都导致umac.c中的这段代码:

/* Message "words" are read from memory in an endian-specific manner.     */
/* For this implementation to behave correctly, __LITTLE_ENDIAN__ must    */
/* be set true if the host computer is little-endian.                     */

#ifndef __LITTLE_ENDIAN__
#if __i386__ || __alpha__ || _M_IX86 || __LITTLE_ENDIAN
#define __LITTLE_ENDIAN__ 1
#else
#define __LITTLE_ENDIAN__ 0
#endif
#endif

在我的平台上,__i386____alpha___M_IX86未定义。关键在__LITTLE_ENDIAN

编译时:

  • -std=c99__LITTLE_ENDIAN 已定义=> #define __LITTLE_ENDIAN__ 0
  • 没有-std=c99__LITTLE_ENDIAN 已定义=> #define __LITTLE_ENDIAN__ 1

硬编码#define __LITTLE_ENDIAN__ 1,无论有没有-std=c99,一切都会开始正常运作。


结论:__LITTLE_ENDIAN是一个特定于gcc的宏,用于确定字节序;看来,-std=c99会影响这个宏(如果使用该选项则没有定义),这会导致不同的(错误的)结果。


<强> 修改
我当前的(“临时”)解决方案是更新有问题的预处理器if语句。我知道这远不是解决这个问题的最佳方法,但检测字节顺序似乎并不那么容易,而且远非琐碎。

运行时检查似乎更可靠,但这会导致代码中的更多更改,这是我想要避免的。看起来,最“无害”的“解决方案”是更新和“修复”当前的解决方案。

所以,因为我只需要它(现在)与GCC合作,我做了以下修改:

#ifndef __LITTLE_ENDIAN__
    #if __GNUC__
        #include <endian.h>
        #if __BYTE_ORDER == __LITTLE_ENDIAN
            #define __LITTLE_ENDIAN__ 1
        #elif __BYTE_ORDER == __BIG_ENDIAN
            #define __LITTLE_ENDIAN__ 0
        #else
            #error "Cannot determine endianness! Please update this macro!"
        #endif
    #elif __i386__ || __alpha__ || _M_IX86
        #define __LITTLE_ENDIAN__ 1
    #else
        #warning "Endianness cannot be determined for this platform; using big endian by default! Please be aware and update this macro!"
        #define __LITTLE_ENDIAN__ 0
    #endif
#endif

答案 2 :(得分:2)

这个答案建立在Kiril Kirov现有答案的基础上

Kiril发现了预处理器检查系统无法识别现有平台的问题:

#if __i386__ || __alpha__ || _M_IX86 || __LITTLE_ENDIAN
#define __LITTLE_ENDIAN__ 1
#else
#define __LITTLE_ENDIAN__ 0
#endif

虽然平台是little-endian,但gcc -std=c99模式中没有定义这些标识符。所以这是引用代码的问题;需要更新代码以更好地识别它是否是一个小端平台。

我要做的第一件事就是停止使用默认情况,这样如果平台无法识别,则会生成错误,而不是默默地运行错误:

#if __i386__ || __alpha__ || _M_IX86 || __LITTLE_ENDIAN
#define __LITTLE_ENDIAN__ 1
#elif __arm__  // etc.
#define __LITTLE_ENDIAN__ 0
#else
#error Unrecognized platform, please update this macro
#endif

下一步是实际正确检测您所使用的系统。关于此主题的Here is another thread

您可以做的一件事是发出gcc -std=c99 -dM -E - <<<'',这将导致gcc输出该模式下所有预定义宏的列表;然后你可以寻找有用的东西。就我而言,它有:

#define __i386 1
#define __i686 1

所以可以使用其中任何一个。

专门针对__LITTLE_ENDIAN__的替代方法是通过预处理器算法检测它,描述here - 尽管该页面上的第一个代码示例实际上并不产生预处理常量,因此它不会可用作未来预处理器检查的条件。


解决了这个特定问题后,您仍应尝试在同一代码库中找到其他架构问题实例。看起来作者拼凑了他/她所知道的一些架构宏。例如,一个显着的缺席是_M_IX64。对于_M___i386__的任何其他实例,可能会通过代码库进行深入研究,看看它是否依赖于这些实例。如果是;然后再次尝试将该测试抽象为一个你可以更好地控制的宏。

理想情况下,您可以在整个代码库的单个标头中定义所有此类宏;然后代码库的其余部分只使用已在该头文件中定义的宏。