自我修养-运行库

入口函数和程序初始化

入口函数,入口点

  • 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;
}

你可能感兴趣的:(自我修养-运行库)