我有以下表面上简单的C程序:
#include <stdint.h>
#include <stdio.h>
uint16_t
foo(uint16_t *arr)
{
unsigned int i;
uint16_t sum = 0;
for (i = 0; i < 4; i++) {
sum += *arr;
arr++;
}
return sum;
}
int main()
{
uint32_t arr[] = {5, 6, 7, 8};
printf("sum: %x\n", foo((uint16_t*)arr));
return 0;
}
我们的想法是,我们迭代一个数组,并将它的16位字加起来,忽略溢出。当使用gcc在x86-64上编译此代码并且没有优化时,我得到的似乎是0xb(11)的正确结果,因为它总结了包含5和6的前4个16位字:< / p>
$ gcc -O0 -o castit castit.c
$ ./castit
sum: b
$ ./castit
sum: b
$
通过优化它的另一个故事:
$ gcc -O2 -o castit castit.c
$ ./castit
sum: 5577
$ ./castit
sum: c576
$ ./castit
sum: 1de6
程序会为总和生成不确定的值。
我现在认为它不是编译器错误的位置,这会让我相信程序中存在一些未定义的行为,但是我无法指出具体的事情会导致它。
请注意,当函数foo编译为单独链接的模块时,不会出现问题。
答案 0 :(得分:3)
你打破strict aliasing rule,这确实是UB。这是因为您通过不同类型的指针(arr
将uint32_t
传递给uint16_t
时,将foo(uint16_t*)
的数组char*
别名。
可用于别名其他类型的唯一指针类型是Sub SaveToPDF()
'This is where we will save the file to a PDF
Sheets(Array("Sheet1", "Sheet2")).Select
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, Filename:= _
"insert file path here\insertfilename.pdf", _
IgnorePrintAreas:=False, OpenAfterPublish:=True
。
有关该主题的一些其他阅读材料:http://dbp-consulting.com/tutorials/StrictAliasing.html
答案 1 :(得分:0)
由K&amp; K定义的C语言。 R的设计不是为了简化代码生成,而是为了生成高效的代码。 ANSI等人。添加了一些规则,旨在允许编译器通过假设程序员不会做某些事情来生成更高效的代码,但这样做的方式使得无法干净有效地实现某些类型的算法。符合标准的foo
版本可以接受指向uint16_t
或uint32_t
的指针,如下所示:
uint16_t foo(uint16_t *arr) // Could perhaps use void*
{
unsigned int i;
uint16_t *src = arr;
uint16_t temp;
uint16_t sum = 0;
for (i = 0; i < 4; i++) {
memcpy(&temp, src, sizeof temp);
sum += temp;
src++;
}
return sum;
}
有些编译器会认识到memcpy
可以替换为直接将*src
简单地作为uint16_t
读取的代码,因此上述代码可能会有效运行,尽管它使用{{1} }。但是,在其他一些编译器上,上面的代码运行得非常慢。此外,虽然memcpy
使得C89可以在没有指针使用规则的情况下完成所有可能的事情,但C99添加了额外的规则,使得某些语义基本上无法实现。幸运的是,在您的特定情况下,memcpy将允许以允许某些编译器生成高效代码的方式表达算法。
请注意,许多编译器都包含一个编译语言的选项,该语言通过消除上述指针使用限制来扩展标准C.在gcc上,选项memcpy
可用于此目的。使用该选项可能会严重降低某些类型代码的效率,除非程序员采取措施防止此类降级(尽可能使用-fno-strict-alias
限定符,并将可能混淆的内容复制到局部变量),但在许多情况下可以编写代码,以便restrict
不会对性能造成太大影响。