计算机系统基础 课程笔记
Keruone
2025-1-23
Contents
编译过程
| 源文件 | 编译方法 | 目标文件 |
|---|---|---|
| .c | gcc -E:预处理 | .i |
| .c/.i | gcc -S:汇编 | .s |
| .c/.i/.s | gcc -c:编译 | .o |
| .c/.i/.s/.o | gcc:链接 | .out |
.c 源文件
.i 预处理文件
.s 汇编文件
.o 目标文件
.out 可执行文件
gcc 输出 out文件的命令为: gcc -o out a.c
其中 -o 表示输出文件的名称为 out
查看命令含义
man 命令
tldr 命令
gcc 编译过程
原始输出
keruone@Ubuntu20:~/xuehao$ gcc a.c --verbose
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.3' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-05ho5U/gcc-9-9.4.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.3)
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/9/cc1 -quiet -v -imultiarch x86_64-linux-gnu a.c -quiet -dumpbase a.c -mtune=generic -march=x86-64 -auxbase a -version -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -o /tmp/ccOmYDRk.s
GNU C17 (Ubuntu 9.4.0-1ubuntu1~20.04.3) version 9.4.0 (x86_64-linux-gnu)
compiled by GNU C version 9.4.0, GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-linux-gnu/9/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
GNU C17 (Ubuntu 9.4.0-1ubuntu1~20.04.3) version 9.4.0 (x86_64-linux-gnu)
compiled by GNU C version 9.4.0, GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: d50f9baaf86b1ab1ea2b5e982c779df7
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64'
as -v --64 -o /tmp/cc6jqJDl.o /tmp/ccOmYDRk.s
GNU汇编版本 2.34 (x86_64-linux-gnu) 使用BFD版本 (GNU Binutils for Ubuntu) 2.34
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/9/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/9/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper -plugin-opt=-fresolution=/tmp/ccx8hAck.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/9/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/9 -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/9/../../.. /tmp/cc6jqJDl.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/9/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crtn.o
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64'
解释及参数含义
| 原始输出 | 解释及参数含义 |
|---|---|
keruone@Ubuntu20:~/xuehao$ gcc a.c --verbose |
使用 gcc 编译 a.c 文件,并启用详细输出模式(--verbose)。– --verbose:显示详细的编译过程信息。 |
Using built-in specs. |
使用 GCC 内置的默认配置。 |
COLLECT_GCC=gcc |
当前使用的 GCC 编译器。 |
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper |
使用的 LTO(链接时优化)包装器路径。 – LTO:链接时优化,允许在链接阶段进行跨模块优化。 |
OFFLOAD_TARGET_NAMES=nvptx-none:hsa |
支持的卸载目标(如 GPU 加速)。 – nvptx-none:NVIDIA PTX 卸载目标。– hsa:异构系统架构卸载目标。 |
OFFLOAD_TARGET_DEFAULT=1 |
默认启用卸载目标支持。 |
Target: x86_64-linux-gnu |
目标平台为 x86_64 架构的 Linux 系统。 |
Configured with: ../src/configure -v ... |
GCC 编译器的配置选项,包括支持的编程语言、路径、优化选项等。 – --enable-languages:支持的编程语言(如 C、C++ 等)。– --prefix:安装路径。– --enable-shared:启用共享库支持。– --enable-threads=posix:启用 POSIX 线程支持。 |
Thread model: posix |
使用的线程模型为 POSIX 线程。 |
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.3) |
GCC 版本信息。 |
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' |
当前 GCC 调用的编译选项。 – -v:显示详细输出。– -mtune=generic:优化代码以适应通用 CPU 架构。– -march=x86-64:生成针对 x86-64 架构的代码。 |
/usr/lib/gcc/x86_64-linux-gnu/9/cc1 -quiet -v ... |
调用 GCC 的 C 编译器前端 cc1,将 a.c 编译为汇编代码 /tmp/ccOmYDRk.s。– -quiet:减少输出信息。– -v:显示详细输出。– -dumpbase:指定生成的汇编文件的基础名称。– -mtune=generic:优化代码以适应通用 CPU 架构。– -march=x86-64:生成针对 x86-64 架构的代码。 |
GNU C17 (Ubuntu 9.4.0-1ubuntu1~20.04.3) version 9.4.0 ... |
使用的 C 语言标准为 C17,编译器版本为 9.4.0。 |
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 |
GCC 的垃圾回收启发式参数设置。 – ggc-min-expand:控制垃圾回收的最小扩展比例。– ggc-min-heapsize:控制垃圾回收的最小堆大小。 |
ignoring nonexistent directory ... |
忽略不存在的目录路径。 |
#include "..." search starts here: |
开始搜索用户自定义头文件路径。 |
#include <...> search starts here: |
开始搜索系统头文件路径。 |
/usr/lib/gcc/x86_64-linux-gnu/9/include |
系统头文件搜索路径之一。 |
/usr/local/include |
系统头文件搜索路径之一。 |
/usr/include/x86_64-linux-gnu |
系统头文件搜索路径之一。 |
/usr/include |
系统头文件搜索路径之一。 |
End of search list. |
头文件搜索路径结束。 |
Compiler executable checksum: d50f9baaf86b1ab1ea2b5e982c779df7 |
编译器可执行文件的校验和。 |
as -v --64 -o /tmp/cc6jqJDl.o /tmp/ccOmYDRk.s |
调用汇编器 as,将汇编代码 /tmp/ccOmYDRk.s 编译为目标文件 /tmp/cc6jqJDl.o。– -v:显示详细输出。– --64:生成 64 位目标文件。– -o:指定输出文件名。 |
GNU汇编版本 2.34 (x86_64-linux-gnu) 使用BFD版本 (GNU Binutils for Ubuntu) 2.34 |
使用的汇编器版本信息。 |
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/ |
编译器工具链的搜索路径。 |
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/ |
库文件的搜索路径。 |
/usr/lib/gcc/x86_64-linux-gnu/9/collect2 ... |
调用链接器 collect2,将目标文件 /tmp/cc6jqJDl.o 链接为最终的可执行文件。– -plugin:指定 LTO 插件。– -plugin-opt:传递给插件的选项。– -dynamic-linker:指定动态链接器。– -pie:生成位置无关的可执行文件。– -z now:启用立即绑定。– -z relro:启用只读重定位。 |
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' |
最终 GCC 调用的编译选项。 |
奇怪的示例代码:
- 以下代码输出为何?
extern int printf(const char *__restrict __format, ...);
int main(){
#if aa == bb
printf("YES\n");
#else
printf("NO\n");
#endif
}
解析:
“`#if aa == bb:“`中“`#if“` 是 C 预处理器指令,用于条件编译。“`aa“` 和 “`bb“` 是未定义的宏。而对于未定义宏,在预处理器中,会被替换为 “`0“`。
因此,“`aa“` 和 “`bb“` 都被替换为 “`0“`,表达式 “`aa == bb“` 等价于 “`0 == 0“`,结果为真“`(1)“`。
所以,预处理器会将 “`#if aa == bb“` 后面的代码块 “`printf(“YES\n”);“` 保留下来,而 “`#else“` 后面的代码块 “`printf(“NO\n”);“` 会被删除。
编译结果:
//#include <stdio.h>
extern int printf(const char *__restrict __format, ...);
int main(){
#if aa == bb
printf("YES\n");
#else
printf("NO\n");
#endif
}
宏展开
即通过复制黏贴的方式来替换宏,直到没有宏可以再展开
#include<stdio.h>
#define NAMES(x)\
x(Tom) x(Jerry) x(Spike)
int main(){
#define PRINT(x) puts("Hello, " #x "!"); //#x 表示将 x 替换为字符串
NAMES(PRINT)
}
以下是使用 Markdown 格式整理的内容:
C 程序执行的两个视角
- 静态:C 代码的连接一段总能对应到一段连接的机器指令。
- 动态:C 代码执行的状态总能对应到机器的状态。
源代码视角
- 函数、变量、指针……
机器指令视角
- 寄存器、内存、地址……
两个视角的共同之处:内存
- 代码、变量(源代码视角)= 地址 + 长度(机器指令视角)
- (不大于逻辑) 内存 = 代码 + 数据 + 堆栈
- 因此,理解 C 程序执行最重要的就是内存模型。
一切皆可取地址
代码案例1
void printptr(void *p) {
printf("p = %p; *p = %016lx\n", p, *(long *)p);
}
int x;
int main(int argc, char *argv[]) {
printptr(main); // 代码
printptr(&main);
printptr(&x); // 数据
printptr(&argc); // 堆栈
printptr(argv);
printptr(&argv);
printptr(argv[0]);
}
输出:
p = 0x55678f95d17b; *p = e5894855fa1e0ff3
p = 0x55678f95d17b; *p = e5894855fa1e0ff3
p = 0x55678f960014; *p = 0000000000000000
p = 0x7ffcab646dbc; *p = 0000000000000001
p = 0x7ffcab646eb8; *p = 00007ffcab647302
p = 0x7ffcab646db0; *p = 00007ffcab646eb8
p = 0x7ffcab647302; *p = 0074756f2e642f2e
代码案例2
int main(int argc, char *argv[]) {
int (*f)(int, char *[]) = main;
if (argc != 0) {
char ***a = &argv, *first = argv[0], ch = argv[0][0];
printf("arg = \"%s\"; ch = '%c'\n", first, ch);
assert(***a == ch);
f(argc - 1, argv + 1);
}
}
其中argc是命令行参数的个数,argv是命令行参数的数组。
下面介绍一个命令行调用案例,并解释什么参数给到argc,什么参数给到argv(假设该文件为test.out)
./test.out hello world
当我们运行
./test.out hello world时,命令行参数的个数 argc 为 3,分别是:
./test.out:第一个参数是程序的名称,它是命令行中第一个参数,并且通常是可执行文件的路径。
hello:第二个参数是hello。
world:第三个参数是world。argc会记录命令行参数的个数,argv会记录命令行参数的数组。
命令行输入
$ ./test.out hello world
输出为
arg = "./c.out"; ch = '.'
arg = "Hello"; ch = 'H'
arg = "Word"; ch = 'W'
这段代码的argc的存储可视为32位(4字节),argv参数为指针所以存储为64位(8字节)。
Q: 为什么 ***a ch?
A:
1. char a = &argv:
argv 是一个 char ** 类型(指向字符串数组的指针)。
&argv 是 argv 的地址,因此 a 是一个 char *** 类型(指向 argv 的指针)。
2. ***a 的解引用过程:
a 是 char *** 类型,指向 argv。
*a 是 argv 本身,类型为 char **。
**a 是 argv[0],类型为 char *(指向第一个参数字符串的指针)。
***a 是 argv[0][0],类型为 char(第一个参数字符串的第一个字符)。
3. ch 的值:
ch 是 argv[0][0],即第一个参数字符串的第一个字符。
4. assert(a ch):
a 解引用后得到 argv[0][0],也就是 ch。
因此,a ch 恒成立,断言总是为真。
其它代码:
- 删除当前文件夹下的所有文件和文件夹:
rm -rf ./* - 查看上一条命令的 退出状态码
echo $? - Vim 编辑器中的一个命令,用于将当前文件或选中的内容以 十六进制格式 显示。这个功能通常用于查看或编辑二进制文件。
:%!xxdvi $(fzf)是一个结合了vi编辑器和fzf(模糊查找工具)的命令,用于通过模糊查找选择文件并用vi打开
示例
假设当前目录下有如下文件:file1.txt file2.txt file3.txt运行 `vi $(fzf)` 后:
`fzf` 会列出 `file1.txt`、`file2.txt` 和 `file3.txt`。
用户可以输入 `file2` 来快速定位 `file2.txt`。
按下回车后,`vi` 会打开 `file2.txt`。
- 管道和
wc命令cat file.txt | wc -lcat file.txt:输出file.txt文件的内容。|:管道符,将前一个命令的输出作为后一个命令的输入。wc -l:统计输入的行数。- 示例:
如果file.txt内容如下:Line 1 Line 2 Line 3运行 `cat file.txt | wc -l` 后,输出结果为 `3`,表示文件有 3 行。