TLDR;以下代码是否调用未定义(或未指定)的行为?
#include <stdio.h>
#include <string.h>
void printme(void *c, size_t n)
{
/* print n bytes in binary */
}
int main() {
long double value1 = 0;
long double value2 = 0;
memset( (void*) &value1, 0x00, sizeof(long double));
memset( (void*) &value2, 0x00, sizeof(long double));
/* printf("value1: "); */
/* printme(&value1, sizeof(long double)); */
/* printf("value2: "); */
/* printme(&value2, sizeof(long double)); */
value1 = 0.0;
value2 = 1.0;
printf("value1: %Lf\n", value1);
printme(&value1, sizeof(long double));
printf("value2: %Lf\n", value2);
printme(&value2, sizeof(long double));
return 0;
}
在我的x86-64机器上,输出取决于传递给编译器的特定优化标志(gcc-4.8.0,-O0 vs -O1)。
使用-O0,我得到了
value1: 0.000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
value2: 1.000000
00000000 00000000 00000000 00000000 00000000 00000000 00111111 11111111
10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
使用-O1时,我得到了
value1: 0.000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
value2: 1.000000
00000000 00000000 00000000 00000000 00000000 01000000 00111111 11111111
10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
请注意倒数第二行的额外1。此外,在memset之后取消注释打印指令使得1消失。这似乎依赖于两个事实:
我正在使用-std=c99 -Wall -Wextra -Wpedantic
进行编译并且没有得到任何警告,所以我不确定这是否是严格别名违规的情况(但很可能是这样)。传递-fno-strict-aliasing
不会改变任何事情。
上下文是在here描述的HDF5库中发现的错误。 HDF5做了一些调整以找出浮点类型的本机位表示,但如果填充位不保持为零,则会混淆。
所以:
感谢。
编辑:这是printme的代码。我承认我刚刚从某个地方切割和粘贴而没有过多关注它。如果故障发生在这里,我会穿着裤子走到桌边。
void printme(void *c, size_t n)
{
unsigned char *t = c;
if (c == NULL)
return;
while (n > 0) {
int q;
--n;
for(q = 0x80; q; q >>= 1)
printf("%x", !!(t[n] & q));
printf(" ");
}
printf("\n");
}
答案 0 :(得分:2)
虽然C标准允许操作破坏填充位,但我不认为这是您系统上发生的事情。相反,它们从未被初始化,并且GCC只是优化memset
-O1
,因为该对象随后被覆盖。这可能会被-fno-builtin-memset
抑制。
答案 1 :(得分:0)
这是未定义的行为吗?
是。填充位是不确定的(*)。访问不确定的内存might as well be undefined behavior(它在C90中是未定义的行为,而一些C99编译器将其视为未定义的行为。此外,C99基本原理说访问不确定的内存意图是未定义的行为。但C99标准本身并未说明很明显,它只是暗示陷阱表示,并且可能给人的印象是,如果一个人知道一个人没有陷阱表示,那么就可以从不确定的记忆中获得未指定的值。 long double
的填充部分至少是未指定的。
(*)C99的脚注271说“为了在结构对象中对齐而用作填充的''孔'的内容是不确定的。”前面的文本引用了未指定的字节,但这只是因为字节没有陷阱表示。
这是严格的别名违规吗?
我的代码中没有看到任何严格的别名冲突。
答案 2 :(得分:-2)
我在这里看不到任何未定义的东西,甚至没有指明(两个非常不同的东西)。是的,memset()
来电已经过优化。在我的机器上(i86-32),long double是12个字节,在结构和堆栈中填充到16。在你的机器上,它们显然是16个字节,因为sizeof(long double)
正在返回16.两个“printme”输出都不像正确的IEEE 128位浮点格式,所以我怀疑{{1}中还有其他错误此处未显示的函数。