下周六是国庆节第一天, 给自己一个🕊的理由
提前祝大家国庆节快乐!
上次课内容:
本次课内容:
C程序如何从源代码生成指令序列(二进制可执行文件)
Talk is cheap, show me the code!
方法:
阅读工具的日志(查看是否支持verbose
,
log
等选项)
预处理阶段只进行文本粘贴, 不求值
\
)而拆分的字符串#ifdef
/#else
/#endif
#
##
更多信息请RTFM
套路: 借助预处理机制编写不可读代码
#\
define C(c /**/)#c
/*size=3173*/#include<stdio.h>
/*crc=b7f9ecff.*/#include<stdlib.h>
/*Mile/Adele_von_Ascham*/#include<time.h>
typedef/**/int(I);I/*:3*/d,i,j,a,b,l,u[16],v
[18],w[36],x,y,z,k;char*P="\n\40(),",*p,*q,*t[18],m[4];
void/**/O(char*q){for(;*q;q++)*q>32?z=111-*q?z=(z+*q)%185,(k?
k--:(y=z%37,(x=z/37%7)?printf(*t,t[x],y?w[y-1]:95):y>14&&y<33?x
=y>15,printf(t[15+x],x?2<<y%16:l,x?(1<<y%16)-1:1):puts(t[y%28])))
,0:z+82:0;}void/**/Q(I(p),I*q){for(x=0;x<p;x++){q[x]=x;}for(;--p
>1;q[p]=y)y =q[x=rand()%-~p],q[x]=q[p];}char/**/n[999]=C(Average?!nQVQd%R>Rd%
R% %RNIPRfi#VQ}R;TtuodtsRUd%RUd%RUOSetirwf!RnruterR{RTSniamRtniQ>h.oidts<edulc
ni #V>rebmun<=NIPD-RhtiwRelipmocResaelPRrorre#QNIPRfednfi#V__ELIF__R_
Re nifed#V~-VU0V;}V{R= R][ORrahcRdengisnuRtsnocRcitatsVesle#Vfidne#V53556
. .1RfoRegnarRehtRniRre getniRnaRsiR]NIP[R erehwQQc.tuptuoR>Rtxt.tupniR
< R]NIP[R:egasuV_Redulcn i#VfednfiVfednuVenife dVfedfiVQc%Rs%#V);I/**/main(
I( f),char**e){if(f){for(i= time(NULL),p=n,q= n+998,x=18;x;p++){*p>32&&!(
*--q=*p>80&&*p<87?P[*p- 81]:* p)?t [( -- x)]=q+1:q;}if(f-2||(d=atoi
(e[1]))<1||65536<d){;O(" \""); goto O;}srand(i);Q(16,u);i=0;Q(
36,w);for(;i<36; i++){w[i] +=w [i]<26 ? 97:39; }O(C(ouoo9oBotoo%]#
ox^#oy_#ozoou#o{ a#o|b#o}c# o~d#oo-e #oo. f#oo/g#oo0h#oo1i#oo
2j#oo3k#oo4l#o p));for(j =8;EOF -(i= getchar());l+=1){a=1+
rand()%16;for(b =0;b<a||i- main (0,e);b++)x=d^d/4^d/8^d/
32,d= (d/ 2|x<<15)&65535; b|= !l<<17;Q(18,v);for(a=0;a<18;
a++ ){if( (b&(1<<(i=v[a] ))))* m=75+i,O(m),j=i<17&&j<i?i:j;}O(C(
!) ); }O(C(oqovoo97o /n!));i= 0;for(;i<8;O(m))m[2]=35,*m=56+u[i],m[1
]= 75 +i++;O(C(oA!oro oqoo9) );k=112-j*7;O(C(6o.!Z!Z#5o-!Y!Y#4~!X!X#3}
!W !W #2 |!V!V#1{!U!U#0z! T!T#/y!S!S#.x!R!R#-w!Q!Q#ooAv!P!P#+o#!O!O#*t!N!
N# oo >s!M!M#oo=r!L!L#oo<q!K!K# &pIo@:;= oUm#oo98m##oo9=8m#oo9oUm###oo9;=8m#o
o9 oUm##oo9=oUm#oo98m#### o09] #o1:^#o2;_#o3<o ou#o4=a#o5>b#o6?c#o
7@d#o8A e#o 9B f#o:Cg#o; D h#o<Ei #o=Fj#o> Gk#o?Hl#oo9os#####
));d=0 ;} O: for(x=y=0;x<8;++
x)y|= d&(1<<u[x])?
1<< x:0;return
/* :9 */
y ; }
词法分析 -> 语法分析 -> 语义分析 -> 中间代码生成 -> 优化 -> 目标代码生成
借助合适的工具(clang
),
我们来看看每一个阶段都在做什么
clang
功能上等价于gcc
识别并记录源文件中的每一个token
按照C语言的语法将识别出来的token组织成树状结构
按照C语言的语义确定AST中每个表达式的类型
struct + int
)
但大多数编译器并没有把词法分析, 语法分析, 语义分析严格按阶段进行
clang
的-ast-dump
把语义信息也一起输出了
man clang
可以得知clang
的阶段划分中间代码(也称中间表示, IR) = 一种编译器定义的, 面向编译场景的指令集
clang
使用的中间代码叫LLVM IR
,
gcc
的叫GIMPLE
C语言标准对程序执行的要求很宽松
这给编译器优化提供了非常广阔的空间
例: 常数传播
加个volatile试试
#include <stdio.h>
int main() { // compute 1 + 2
int x = 1, y = 2;
volatile int z = x + y;
printf("z = %d\n", z);
return 0;
}
查看clang
进行的优化
将中间代码翻译成目标指令集
可以通过time report观察clang
尝试了哪些优化工作
clang -c a.c
clang -c a.c --target=riscv64 -march=rv64g
# alias rvclang="clang --target=riscv64 -march=rv64g"
根据指令集手册, 把汇编代码(指令的符号化表示)翻译成二进制目标文件(指令的编码表示)
clang a.c
riscv64-linux-gnu-gcc a.c # clang默认调用链接器的参数有点问题, 这里还是用gcc来演示
# apt-get install g++-riscv64-linux-gnu
合并多个目标文件, 生成可执行文件
有很多crtxxx.o
的文件
objdump
确认问题: printf()
的代码在哪里呢?
./a.out
# 通过一些配置工作, RISC-V的可执行文件也可以在本地执行
# apt-get instal qemu-user qemu-user-binfmt
# mkdir -p /etc/qemu-binfmt
# ln -s /usr/riscv64-linux-gnu/ /etc/qemu-binfmt/riscv64
file a.out
a.out: ELF 64-bit LSB pie executable, UCB RISC-V, version 1 (SYSV)...
./a.out # 实际上是在QEMU模拟器中执行
把可执行文件加载到内存, 跳转到程序, 执行编译出的指令序列
Q: 谁来加载?
A: 运行时环境(宿主操作系统/QEMU)
关于执行的更多内容将在下次课介绍
c90/c99和32位/64位组合下的结果
clang -w -std=c90 -m32 a.c && ./a.out
clang -w -std=c99 -m32 a.c && ./a.out
clang -w -std=c90 -m64 a.c && ./a.out
clang -w -std=c99 -m64 a.c && ./a.out
32位 | 64位 | |
---|---|---|
c90 | TT | FT |
c99 | FT | FT |
正确做法: 通过日志观察工具的行为
2147483648
属于无后缀十进制数
int
, long
,
unsigned long
int
, long
,
long long
2147483648
在clang
语法树中的类型
32位 | 64位 | |
---|---|---|
c90 | unsigned long |
long |
c99 | long long |
long |
假设clang
的解析是对的
2^31 + 6
),
不等式成立clang
的解析结果得到一个猜想long
在32位环境下长度是32位, 在64位环境下长度是64位怎么验证/推翻这个猜想?
动手写个小程序就可以啦
表面上看是这样, 但C语言标准真的是这么说的吗?
5.2.4.2 Numerical limits
An implementation is required to document all the limits specified in this
subclause, ...
5.2.4.2.1 Sizes of integer types <limits.h>
... Their implementation-defined values shall be equal or greater in magnitude
(absolute value) to those shown, with the same sign.
C语言标准并没有明确定义类型的长度, 而是全部交给具体实现来定义
C99的Abstract
... Its purpose is to promote portability, reliability, maintainability, and
efficient execution of C language programs on a variety of computing systems.
3.6 byte
NOTE 2 A byte is composed of a contiguous sequence of bits, the number of which is
implementation-defined
int
是16位int
是32位int
还是32位use of an unspecified value, or other behavior where this International Standard
provides two or more possibilities and imposes no further requirements on which is
chosen in any instance
C标准提供了多种行为可选, 具体实现需要选择
例: 函数调用时参数求值顺序是unspecified
#include <stdio.h>
void f(int x, int y) {
printf("x = %d, y = %d\n", x, y);
}
int main() {
int i = 1;
f(i ++, i ++);
return 0;
}
包含这种行为的程序, 重新编译后可能无法得到正确的结果
一类特殊的未指定行为, 具体实现需要将选择写到文档里
例: 类型的长度
包含这种行为的程序, 在相同的环境下运行可以得到相同的结果
behavior, upon use of a nonportable or erroneous program construct or of erroneous
data, for which this International Standard imposes no requirements
程序/数据不符合标准的行为
例: 缓冲区溢出
包含这种行为的程序, 多次运行可能也无法得到正确的结果
C99关于implementation的定义(第5节):
An implementation translates C source files and executes C programs in two
data-processing-system environments, which will be called the translation environment
and the execution environment in this International Standard...
在真实的系统中,
这相当于编译器+(处理器+操作系统+库函数)
这就是ABI(Application Binary Interface), 具体包含
ABI手册是计算机系统软硬件协同的重要体现
Q: ???
A: char
的符号也是implementation-defined的
char
来进行算术运算
signed char
或unsigned char