
下周六是国庆节第一天, 给自己一个🕊的理由
提前祝大家国庆节快乐!
上次课内容:
本次课内容:
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 longint, long,
long long2147483648在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-definedint是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 instanceC标准提供了多种行为可选, 具体实现需要选择
例: 函数调用时参数求值顺序是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