C语言
ANSI C 还是 POSIX C?
在C语言中可以用write或者fwrite将内容写到文件流中,但是查询文档会发现write只有在Linux Maunal里出现(可以通过
man 2 write
在linux中shell里直接查找文档),fwrite在C语言文档中出现。还有很多类似的函数也都有以上的区别。有些人认为是POSIX和ANSI C上不同的函数。
C语言的标准有以下几种
- 1989年ANSI C标准,在GCC编译器是
C89
- 1999年ISO C标准,在GCC编译器是
C99
- 目前是 标准 C(standard C) GNU文档并没有说清楚standard C是什么。cpp参考列出了详细的C标准历史.
当使用man 2 wirte
时,文档开头标志这是一个System Call
,而且作为标准C库的一个函数。查看GNU C库文档,可以发现GNU C库使用的是ISO POSIX标准,它是ISO C的超集,里面提供里除了ISO C标准要求的函数,还提供对特定操作系统底层的支持。而ISO C中并没有write
这个函数,所以可以证实这个函数来自POSIX标准。另外Microsoft文档也证实这是一个POSIX标准的函数。
预处理
参考一生一芯
预处理等同于替换文本
- 头文件替换
- 宏替换
可以使用gcc
的-E
参数查看预处理的结果,可以加上verbose
显示一些额外信息
gcc -E a.c
gcc -E a.c -v > /dev/null
-E
:停在预处理阶段-v
或者--verbose
: 将执行的命令输出到标准错误输出>/dev/null
将标准输出重定向到/dev/null
-I
: 指定头文件包含路径,默认顺序- 对于双引号包括的头文件,当前文件所在目录
- 对于双引号包括的头文件,查找被
-iquote
指定的目录 -I
指定的目录-isystem
指定的目录- 标准系统目录
-idirafter
指定的目录
编译
编译包括多个阶段,借助clang
可以看到各个阶段的步骤,功能等价与gcc,但是可以更好的展示编译的中间步骤
- 词法分析: 识别并记录源文件的每一个token
clang -fsyntax-only -Xclang -dump-tokens a.c
- 语法分析:将token组成树状结构(AST, Abstract Syntax Tree),报告语法错误
clang -fsyntax-only -Xclang -ast-dump a.c
- 语义分析:按照C语言的语义确定AST中每个表达式的类型,clang的-ast-dump把语义信息也一起输出了
- 静态程序分析:
clang a.c --analyze -Xanalyzer -analyzer-output=text
- 静态程序分析:
- 中间代码生成:中间表示(IR) = 编译器定义的, 面向编译场景的指令集;将C语言状态机翻译成IR状态机
clang -S -emit-llvm a.c
- 优化:比如下面代码例子将常数预先计算出来(常数传播)
- 对volatile修饰变量的访问需要严格执行,因此不会被常数传播优化影响
- 程序结束时, 写入文件的数据需要与严格执行时一致
- 交互式设备的输入输出(stdio.h)需要与严格执行时一致
clang -S -foptimization-record-file=- a.c
clang -S -foptimization-record-file=- a.c -O1
- 目标代码生成: 将IR状态机翻译成处理器ISA状态机;ISA相关优化,通过time report观察clang尝试了哪些优化工作,
clang -S a.c -ftime-report
clang -S a.c
clang -S a.c --target=riscv32-linux-gnu
gcc -S a.c # 也可以用gcc生成
# apt-get install g++-riscv64-linux-gnu
riscv64-linux-gnu-gcc -march=rv32g -mabi=ilp32 -S a.c
汇编
根据指令集手册, 把汇编代码(指令的符号化表示)翻译成二进制目标文件(指令的编码表示)
gcc -c a.c
riscv64-linux-gnu-gcc -march=rv32g -mabi=ilp32 -c a.c
# alias rv32gcc="riscv64-linux-gnu-gcc -march=rv32g -mabi=ilp32"
二进制文件不能用文本编辑器打开来阅读了,需要binutils(Binary Utilities)或者llvm的工具链
objdump -d a.o
riscv64-linux-gnu-objdump -d a.o
# alias rvobjdump="riscv64-linux-gnu-objdump"
llvm-objdump -d a.o # llvm的工具链可以自动识别目标文件的架构, 用起来更方便
链接
gcc a.c --verbose
gcc a.c --verbose 2>&1 | tail -n 2 | head -n 1 | tr ' ' '\n' | grep '\.o$'
有很多crtxxx.o的文件
- crt = C runtime, C程序的运行时环境(的一部分)
- 可以通过objdump确认
实现定义行为和ABI(Application Binary Interface)
- 只定义了类型的最小范围
- 未指定行为(Unspecified Behavior)C标准提供了多种行为可选, 具体实现需要选择
- 实现定义行为(Implementation-defined Behavior)
- 未定义行为(Undefined Behavior)程序/数据不符合标准的行为,完全没说会发生什么, 一切皆有可能
ABI(Application Binary Interface), 具体包含
- 处理器的指令集, 寄存器结构, 栈的组织, 访存类型等
- 处理器可直接访问的基本数据类型的大小, 布局, 对齐方式
- 调用约定, 用于规定函数的参数如何传递, 返回值如何获取
- 应用程序如何向操作系统发起系统调用
- 目标文件的格式, 支持的运行库等
ABI手册是计算机系统软硬件协同的重要体现
- 程序的运行结果与源代码, 编译器, 运行时环境, OS, 硬件等都有关系
环境变量
在c程序中打印环境变量
extern char **environ;
int main(int argc,char **argv)
{
printf("%p\n", (void*)environ);
}