入口函数和程序初始化
入口函数,入口点
-
atexit
是一个特殊的函数,它接受一个函数指针作为参数,并保证程序正常退出时,这个函数指针指向的函数会被调用。
例如
#include
#include
void foo() {
printf("Good Bye!!\n");
}
int main() {
atexit(&foo);
printf("at the end of main!\n");
}
用atexit
函数注册的函数调用时机是在main结束之后的,因此输出为
at the end of main!
Good Bye!!
入口函数的实现
MSVC CRT入口函数
- 初始化和OS版本有关的全局变量
- 初始化堆
- 初始化I/O
- 初始化命令行参数和环境变量
- 初始化C库的一些数据
- 调用main并记录返回值
- 检查错误并将main返回值返回
msvc的入口函数使用了alloca函数
C/C++运行库
C语言标准库
变长参数
非局部跳转
(是的,这,绝对不是结构化编程)
#include
#include
jmp_buf b;
void f() {
longjmp(b, 1);
}
int main() {
if (setjmp(b)) {
printf("World ");
}
else {
printf("Hello ");
f();
}
return 0;
}
这段代码的实际输出是
Hello World
实际上,当setjmp正常返回的时候,会返回0,因此会打印出Hello,而longjmp的作用,就是把程序执行流回到当初setjmp的时候,并且返回由longjmp指定的返回值(即longjmp的第二个参数),也就是1,自然会打印出World. 也就是说,longjmp可以让程序时光倒流回setjmp返回的时刻,并改变行为,改变未来~~~
glibc和MSVC CRT
Linux和Windows平台下的两个主要C语言运行库分别为glibc(GNU C Library)和MSVCRT(Microsoft Visual C Run-time).
值得注意的是,类似线程操作这种功能并不是标准的C语言运行库的一部分,但是glibc和MSVCRT都包含了线程操作的库函数。所以,glibc和MSVCRT事实上是标准C语言运行库的超集,他们各自对C标准库进行了一些拓展。
运行库和多线程
CRT改进
- 使用TLS
线程局部存储
(TLS,Thread Local Storage),线程私有的变量。如果要定义一个全局变量为TLS,只需要在定义前加上相应的关键字即可。
对于GCC来说,为__thread
,例如__thread int number
.
对于MSVC来说,为__declspec(thread)
,例如__declspec(thread) int number
在main前调用函数
glibc的全局构造函数是放置在.ctors
段里的,因此如果我们手动在.ctors
段里添加一些函数指针,就可以让函数在全局构造的时候(main之前)调用:(GCC中)
#include
void my_init(){
printf("init now\n");
}
typedef void (*ctor_t)(void);
//在.ctors段里添加一个函数指针
ctor_t __attribute__((section(".ctors"))) test_p=&my_init;
int main(){
printf("main now\n");
return 0;
}
输出结果
init now
main now
当然,事实上,GCC还有更加直接的方法来达到同样的效果,即使用__attribute((constructor))
#include
void my_init() __attribute__((constructor));
void my_init(){
printf("init now\n");
}
int main(){
printf("main now\n");
return 0;
}