mirror of
https://github.com/nganhkhoa/CTF-All-In-One.git
synced 2024-12-25 11:41:16 +07:00
147 lines
4.6 KiB
Markdown
147 lines
4.6 KiB
Markdown
# C 语言基础
|
||
|
||
- [从源代码到可执行文件](#从源代码到可执行文件)
|
||
- [整数表示](#整数表示)
|
||
|
||
|
||
## 从源代码到可执行文件
|
||
我们以经典著作《The C Programming Language》中的第一个程序 “Hello World” 为例,讲解 Linux 下 GCC 的编译过程。
|
||
|
||
```c
|
||
#include <stdio.h>
|
||
main()
|
||
{
|
||
printf("hello, world\n");
|
||
}
|
||
```
|
||
|
||
```text
|
||
$gcc hello.c
|
||
$./a.out
|
||
hello world
|
||
```
|
||
|
||
以上过程可分为4个步骤:预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。
|
||
|
||
![](../pic/1.5.1_compile.png)
|
||
|
||
#### 预编译
|
||
```text
|
||
$gcc -E hello.c -o hello.i
|
||
```
|
||
|
||
预编译过程主要处理源代码中以 “#” 开始的预编译指令:
|
||
- 将所有的 “#define” 删除,并且展开所有的宏定义。
|
||
- 处理所有条件预编译指令,如 “#if”、“#ifdef”、“#elif”、“#else”、“#endif”。
|
||
- 处理 “#include” 预编译指令,将被包含的文件插入到该预编译指令的位置。注意,该过程递归执行。
|
||
- 删除所有注释。
|
||
- 添加行号和文件名标号。
|
||
- 保留所有的 #pragma 编译器指令。
|
||
|
||
#### 编译
|
||
```text
|
||
$gcc -S hello.c -o hello.s
|
||
```
|
||
编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。
|
||
|
||
#### 汇编
|
||
```text
|
||
$gcc -c hello.s -o hello.o
|
||
或者
|
||
$gcc -c hello.c -o hello.o
|
||
```
|
||
汇编器将汇编代码转变成机器可以执行的指令。
|
||
|
||
#### 链接
|
||
目标文件需要链接一大堆文件才能得到最终的可执行文件。链接过程主要包括地址和空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定向(Relocation)等。
|
||
|
||
|
||
## 整数表示
|
||
默认情况下,C 语言中的数字是有符号数,下面我们声明一个有符号整数和无符号整数:
|
||
```c
|
||
int var1 = 0;
|
||
unsigned int var2 = 0;
|
||
```
|
||
- 有符号整数
|
||
- 可以表示为正数或负数
|
||
- `int` 的范围:`-2,147,483,648 ~ 2,147,483,647`
|
||
- 无符号整数
|
||
- 只能表示为零或正数
|
||
- `unsigned int` 的范围:`0 ~ 4,294,967,295`
|
||
|
||
`signed` 或者 `unsigned` 取决于整数类型是否可以携带标志 `+/-`:
|
||
- Signed
|
||
- int
|
||
- signed int
|
||
- long
|
||
- Unsigned
|
||
- unit
|
||
- unsigned int
|
||
- unsigned long
|
||
|
||
在 `signed int` 中,二进制最高位被称作符号位,符号位被设置为 `1` 时,表示值为负,当设置为 `0` 时,值为非负:
|
||
- 0x7FFFFFFF = 2147493647
|
||
- 01111111111111111111111111111111
|
||
- 0x80000000 = -2147483647
|
||
- 10000000000000000000000000000000
|
||
- 0xFFFFFFFF = -1
|
||
- 11111111111111111111111111111111
|
||
|
||
二进制补码以一种适合于二进制加法器的方式来表示负数,当一个二进制补码形式表示的负数和与它的绝对值相等的正数相加时,结果为 0。首先以二进制方式写出正数,然后对所有位取反,最后加 1 就可以得到该数的二进制补码:
|
||
```text
|
||
eg: 0x00123456
|
||
= 1193046
|
||
= 00000000000100100011010001010110
|
||
~= 11111111111011011100101110101001
|
||
+= 11111111111011011100101110101010
|
||
= -1193046 (0xFFEDCBAA)
|
||
```
|
||
|
||
编译器需要根据变量类型信息编译成相应的指令:
|
||
- 有符号指令
|
||
- IDIV:带符号除法指令
|
||
- IMUL:带符号乘法指令
|
||
- SAL:算术左移指令(保留符号)
|
||
- SAR:右移右移指令(保留符号)
|
||
- MOVSX:带符号扩展传送指令
|
||
- JL:当小于时跳转指令
|
||
- JLE:当小于或等于时跳转指令
|
||
- JG:当大于时跳转指令
|
||
- JGE:当大于或等于时跳转指令
|
||
- 无符号指令
|
||
- DIV:除法指令
|
||
- MUL:乘法指令
|
||
- SHL:逻辑左移指令
|
||
- SHR:逻辑右移指令
|
||
- MOVZX:无符号扩展传送指令
|
||
- JB:当小于时跳转指令
|
||
- JBE:当小于或等于时跳转指令
|
||
- JA:当大于时跳转指令
|
||
- JAE:当大于或等于时跳转指令
|
||
|
||
32 位机器上的整型数据类型,不同的系统可能会有不同:
|
||
|
||
C 数据类型 | 最小值 | 最大值 | 最小大小
|
||
--- | --- | --- | ---
|
||
char | -128 | 127 | 8 bits
|
||
short | -32 768 | 32 767 | 16 bits
|
||
int | -2 147 483 648 | 2 147 483 647 | 16 bits
|
||
long | -2 147 483 648 | 2 147 483 647 | 32 bits
|
||
long long | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 | 64 bits
|
||
|
||
固定大小的数据类型:
|
||
- `int [# of bits]_t`
|
||
- int8_t, int16_t, int32_t
|
||
- `uint[# of bits]_t`
|
||
- uint8_t, uint16_t, uint32_t
|
||
|
||
更多信息在 `stdint.h` 和 `limits.h` 中:
|
||
```text
|
||
$ man stdint.h
|
||
$ cat /usr/include/stdint.h
|
||
$ man limits.h
|
||
$ cat /usr/include/limits.h
|
||
```
|
||
|
||
了解整数的符号和大小是很有用的,在后面的相关章节中我们会介绍整数溢出的内容。
|