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

我写了一个创建者函数,它应该返回一个指向函数的指针。为了用数据填充结构,我读入了一个文本文件。根据我用作输入的文本文件,错误要么发生,要么不发生。 (错误发生在大约 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/

10-11 08:42