Return 语句未在 c 中执行

Return statement does not get executed in c

所以,我有一个奇怪的案例,无法完全弄清楚我做错了什么。场景如下:

我写了一个创建者函数,它应该 return 一个指向函数的指针。为了用数据填充结构,我读入了一个文本文件。根据我用作输入的文本文件,错误要么发生,要么不发生。 (对于具有 ~4000 行的文本文件会发生错误,而对于具有 ~200 行的文件则不会发生错误,如果这有所不同的话)。奇怪的是代码一直执行到 return 语句之前。但它不会 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 的代码生成中,它恰好指向有效的某个地方,可能是堆栈中的某个地方。对于较大的文件,我们可能会遇到缓冲区溢出,它会覆盖局部变量 and/or 和 return 地址,并以无限循环结束。 考虑到它是偶然发生的,这仍然没有崩溃真是太神奇了。(在禁用优化的 ICC 的 x86-64 asm 中,它只是加载一些未初始化的堆栈内存作为 arg Somestruct_ctor.)

或者它可能是一个指向 stdio 数据结构的指针,在 main 之前从 stdio 的 init 中遗留下来。也许 fillDataFromFileFILE *stdout 指向的所有数据上涂鸦(例如)使其处于 "locked" 状态,因此您的单线程被卡住等待其他东西来解锁互斥锁。 如果您了解 asm,那么在 printf 中单步执行无限循环或 "deadlock" 并查看到底发生了什么可能会很有趣。


如果您使用 gcc -O3 进行编译,编译器会将寄存器置零作为 fillDataFromFile 的参数(在内联 Somestruct_ctor 之后),因此它会传递 NULL 指针。假设函数取消引用指针,那可能总是会崩溃。

clang 选择让 rdi(调用 conventino 的 x86-64 系统 V 中的第一个参数传递寄存器)未初始化,因此当 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)为其获取 space。

自动存储+调用构造函数相当于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
}