我知道C++ 中的"未定义行为"几乎可以让编译器做任何想做的事情.但是,我遇到了让我感到惊讶的崩溃,因为我认为代码足够安全.
在这种情况下,真正的问题仅发生在使用特定编译器的特定平台上,并且仅在启用了优化时才发生.
我尝试了几件事来重现问题并将其简化到最大程度.这是一个名为的函数的摘录Serialize,它将获取bool参数,并将字符串true或复制false到现有的目标缓冲区.
如果bool参数是未初始化的值,那么这个函数是否会在代码审查中,没有办法告诉它实际上可能会崩溃?
// Zero-filled global buffer of 16 characters
char destBuffer[16];
void Serialize(bool boolValue) {
// Determine which string to print based on boolValue
const char* whichString = boolValue ? "true" : "false";
// Compute the length of the string we selected
const size_t len = strlen(whichString);
// Copy string into destination buffer, which is zero-filled (thus already null-terminated)
memcpy(destBuffer, whichString, len);
}
Run Code Online (Sandbox Code Playgroud)
如果使用clang 5.0.0 +优化执行此代码,它将/可能崩溃.
boolValue ? "true" …
我不太熟悉C标准,所以请耐心等待.
我想知道,按标准保证memcpy(0,0,0)是否安全.
我能找到的唯一限制是,如果内存区域重叠,那么行为是未定义的......
但我们可以认为这里的内存区域重叠吗?
正如其他地方所解释的那样,调用memcpy无效或NULL指针等函数是未定义的行为,即使length参数为零.在这样的功能,特别是上下文memcpy和memmove,是一个指针刚刚过去的阵列的有效指针的结束?
我问这个问题是因为一个指针刚好超过一个数组的末尾是合法的(相反,例如一个指针超过一个数组末尾的两个元素),但你不能取消引用它,但是脚注106 ISO 9899:2011表明这样的指针指向程序的地址空间,这是指针根据§7.1.4有效所需的标准.
这种用法发生在我希望将项目插入数组中间的代码中,要求我在插入点之后移动所有项目:
void make_space(type *array, size_t old_length, size_t index)
{
memmove(array + index + 1, array + index, (old_length - index) * sizeof *array);
}
Run Code Online (Sandbox Code Playgroud)
如果我们想在数组的末尾插入,index则等于length并array + index + 1指向刚好超过数组的末尾,但复制元素的数量为零.
我正在讨论使用具有不确定值的变量导致未指定的行为,而不是未定义的行为,如此处所述。这假设具有自动存储持续时间的变量已获取其地址并且陷阱表示不适用。
在具体情况下,讨论了ptr之后发生的情况free(ptr),在这种情况下 C17 6.2.4 适用:
当指针指向(或刚刚过去)的对象到达其生命周期结束时,指针的值变得不确定。
我做了这个例子:
#include <stdlib.h>
#include <stdio.h>
int main (void)
{
int* ptr = malloc(sizeof *ptr);
int* garbage;
int*volatile* dummy = &garbage; // take the address
free(ptr);
puts("This should always print");
fflush(stdout);
if(ptr == garbage)
{
puts("Didn't see that one coming.");
}
else
{
puts("I expect this to happen");
}
puts("This should always print");
}
Run Code Online (Sandbox Code Playgroud)
我提出的论点是,从理论上讲,我们无法知道是ptr == garbage真是假,因为此时它们都是不确定的。因此编译器甚至不需要读取这些内存位置 - 因为它可以推断两个指针都保存不确定的值,所以在优化期间可以根据需要自由地将表达式评估为 true 或 false。(实际上大多数编译器可能不会这样做。)
我在 x86_64 编译器 …