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 中遗留下来。也许 fillDataFromFile
在 FILE *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
}
所以,我有一个奇怪的案例,无法完全弄清楚我做错了什么。场景如下:
我写了一个创建者函数,它应该 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 中遗留下来。也许 fillDataFromFile
在 FILE *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
}