C语言

ANSI C 还是 POSIX C?

在C语言中可以用write或者fwrite将内容写到文件流中,但是查询文档会发现write只有在Linux Maunalopen in new window里出现(可以通过man 2 write在linux中shell里直接查找文档),fwrite在C语言文档open in new window中出现。还有很多类似的函数也都有以上的区别。有些人认为是POSIX和ANSI C上不同的函数open in new window

C语言的标准有以下几种

当使用man 2 wirte时,文档开头标志这是一个System Call,而且作为标准C库的一个函数。查看GNU C库文档open in new window,可以发现GNU C库使用的是ISO POSIX标准,它是ISO C的超集,里面提供里除了ISO C标准要求的函数,还提供对特定操作系统底层的支持。而ISO Copen in new window中并没有write这个函数,所以可以证实这个函数来自POSIX标准。另外Microsoft文档open in new window也证实这是一个POSIX标准的函数。

预处理

参考一生一芯open in new window

预处理等同于替换文本

  • 头文件替换
  • 宏替换

可以使用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);
}
Last Updated:
Contributors: greatofdream