所以,我有一个奇怪的案例,无法弄清楚我做错了什么。这是场景:
我写了一个创建者函数,它应该返回一个指向函数的指针。为了用数据填充结构,我读入了一个文本文件。根据我用作输入的文本文件,错误要么发生,要么不发生。 (错误发生在大约 4000 行的文本文件中,而不是发生在大约 200 行的文件中,如果这有所不同的话)。奇怪的是,代码一直执行到 return
语句之前。但它然后不会返回而只是挂起。没有错误,没有编译器警告(英特尔编译器)。我想知道是否有人经历过类似的事情或知道可能会出现什么问题。
下面的代码被简化以说明问题。实际代码稍微复杂一些,因为我使用 Schreiner 的方法来处理 C 中的对象。
struct Somestruct {
int A;
int B;
int C;
}
static void *Somestruct_ctor(void *_self) {
struct Somestruct *self = _self;
fillDataFromFile(self);
printf("This line gets executed.\n");
return self; // <- this one doesn't
}
int main(int argc, char *argv[]) {
void *myObject;
myObject = Somestruct_ctor(myObject);
printf("The code does NOT get until here\n");
return 0;
}
最佳答案
void * myObject;
未初始化,且未指向有效存储。 读取其值(将其作为 arg 按值传递给 Somestruct_ctor(myObject)
)是未定义的行为。
您的代码并不总是崩溃的事实告诉我们,在 ICC 的代码生成中,它恰好指向有效的某个地方,可能是堆栈中的某个地方。对于较大的文件,我们可能会出现缓冲区溢出,覆盖局部变量和/或返回地址并以无限循环结束。 令人惊讶的是,鉴于它是偶然发生的,它仍然没有崩溃。 (在禁用优化的 ICC 的 x86-64 asm 中,它只是加载一些未初始化的堆栈内存作为 Somestruct_ctor
的参数。)
或者它可能是一个指向 stdio 数据结构的指针,在 main
之前从 stdio 的 init 遗留下来。也许在 fillDataFromFile
指向的数据上涂满 FILE *stdout
(例如)使其处于“锁定”状态,因此您的单线程卡住了,等待其他东西解锁互斥锁。 如果您了解 asm,那么在 printf
中单步执行无限循环或“死锁”并查看到底发生了什么可能会很有趣。
如果您使用 gcc -O3
编译,编译器会将寄存器归零作为 fillDataFromFile
的 arg(在内联 Somestruct_ctor
之后),因此它会传递一个 NULL 指针。假设函数取消引用指针,那可能总是崩溃。
clang 选择不初始化 rdi
(x86-64 System V 调用 conventino 中的第一个 arg-passing 寄存器),因此当 argc
调用 main
时它仍然保留 fillDataFromFile
。这也会可靠地崩溃。
您忘记启用编译器警告。
所有主要的 x86 编译器(gcc、clang、MSVC、ICC)都有这方面的警告,但并非所有编译器都默认启用(仅在 MSVC 中)。可能是因为在某些情况下,如果存在一些条件性的东西,编译器可能不确定 var 是否未初始化。在这种情况下,可以 100% 确定它确实未初始化地使用,但是如果 init 和 use 位于不同的 if()
块内,编译器可能无法证明仅在 init 发生时才使用。
使用 clang 和 gcc,您通常应该使用 -Wall
并使所有警告静音。
对于 ICC, -diag-enable:warn
更接近 gcc -Wall
。 (ICC 的 -Wall
没有启用这个非常重要的警告。不要误以为你已经用 icc -Wall
启用了所有重要的警告。)
# from icc -diag-enable:warn on your code
<source>(21): warning #592: variable "myObject" is used before its value is set
myObject = Somestruct_ctor(myObject);
^
how to turn on icc/icpc warnings? 有一些信息。与 gcc 的 相比,icc 的
-Wall
非常简洁。所以也许 -Wall -Wextra
对 icc 有用。它推荐 -w2
或 -w3
作为潜在有用的警告级别。Clang 通常有最好的警告,在这种情况下:
<source>:21:30: warning: variable 'myObject' is uninitialized when used here [-Wuninitialized]
myObject = Somestruct_ctor(myObject);
^~~~~~~~
<source>:19:18: note: initialize the variable 'myObject' to silence this warning
void * myObject;
^
= NULL
1 warning generated.
我通过编译你的源代码 on the Godbolt compiler explorer 得到了上面的输出(在修复了语法错误之后:结构后缺少分号,以及
Struct
关键字的大写。)-xc
告诉 Godbolt 上的 C++ 编译器编译为 C。事实证明,您无需为 icc 和 gcc 启用优化即可注意到此错误。一些警告仅在启用优化的情况下起作用,编译器对程序逻辑进行更多分析并且可以注意到更多,但它们甚至在
-O0
处跟踪未初始化。更有意义的构造函数代码:
// C
int main(void){
struct Somestruct myObject; // automatic storage for the object value
Somestruct_ctor(&myObject); // pass a pointer to that storage
}
该对象需要住在某处。我们可以使用自动(本地)、静态(
static
本地或全局)或动态存储( malloc
)为其获取空间。自动存储+调用构造函数等价于C++这样,如果
struct Somestruct
在struct/class定义中声明了C++默认构造函数。// C++
int main(void){
Somestruct myObject; // calls the default constructor, if there is one
// destructor will be called at some point when myObject goes out of scope
}
关于c - 返回语句不会在 c 中执行,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/54015817/