关于面试的问题_1
注: 来自AI 和自己的搜索
gcc 编译的完整流程
主要是链接阶段动态库的作用
1. 预处理(Preprocessing)
预处理阶段由预处理器(如cpp
)负责,将源代码中的预处理指令进行处理。
- 宏替换:展开代码中的宏定义,如
#define
定义的符号会被实际的值或表达式替换。- 例如,
#define PI 3.14
,在代码中PI
会被替换为3.14
。
- 例如,
- 头文件包含:将头文件中的内容插入到包含指令
#include
的地方。- 例如,
#include <stdio.h>
会将stdio.h
文件的内容插入到该指令处。
- 例如,
- 条件编译:根据条件编译指令(如
#ifdef
、#ifndef
、#if
等)的条件,决定是否包含特定的代码块。- 例如,
#ifdef DEBUG
只有在定义了DEBUG
宏的情况下,才会包含其中的代码。
- 例如,
- 注释删除:去除代码中的注释。
- 例如,
/* this is a comment */
和// this is a comment
都会被删除。
- 例如,
工具和命令:
gcc -E source.c -o source.i
该命令将预处理后的代码保存为source.i
文件,这个文件是纯C语言代码,已经展开了宏、包含了头文件,并删除了注释。
2. 编译(Compilation)
编译阶段由编译器前端(如cc1
)负责,将预处理后的代码转换为汇编代码。这个阶段主要包括以下步骤:
- 词法分析:扫描预处理后的代码,将代码分割成一个个称为“记号”(Token)的基本元素,如关键字、标识符、操作符等。
- 例如,将
int a = 5;
分解为int
、a
、=
、5
和;
等记号。
- 例如,将
- 语法分析:将词法分析生成的记号流组织成语法树(AST),以理解代码的结构。
- 例如,
int a = 5;
被解析为变量声明语句,其中a
是int
类型,并初始化为5
。
- 例如,
- 语义分析:检查语法树的语义,确保代码符合语言的语义规则,如类型检查、作用域检查等。
- 例如,确保
a
的类型是int
,而不是float
或其他不兼容的类型。
- 例如,确保
- 中间代码生成:将语法树转化为一种中间表示形式,通常是独立于机器的三地址代码或其他中间表示(IR)。
- 例如,将
a = b + c;
表示为t1 = b + c; a = t1;
。
- 例如,将
- 优化:对中间代码进行各种优化,提升运行效率和减少代码大小。
- 例如,删除冗余代码、进行循环优化、寄存器分配等。
- 汇编代码生成:将优化后的中间代码转换为目标机器的汇编代码。
- 例如,将
a = b + c;
生成对应的汇编指令。
- 例如,将
工具和命令:
gcc -S source.i -o source.s
该命令生成汇编代码文件source.s
。
3. 汇编(Assembly)
汇编阶段由汇编器(如as
)负责,将汇编代码转换为二进制的目标文件(即机器码)。
- 符号表生成:在汇编过程中,汇编器生成一个符号表,用于存储变量名、函数名与它们在内存中位置的对应关系。
- 机器码生成:汇编器将每条汇编指令转换为目标机器的二进制指令。
- 例如,将
movl $5, %eax
转换为相应的二进制代码。
- 例如,将
- 生成目标文件:最终生成的目标文件(.o文件)包含了机器码和符号表,但还未解决外部符号(即不同模块之间的符号引用)。
工具和命令:
gcc -c source.s -o source.o
该命令生成目标文件source.o
。
4. 链接(Linking)
链接阶段由链接器(如ld
)负责,将多个目标文件和库文件链接成最终的可执行文件。这个阶段包括以下步骤:
- 符号解析:链接器解析各目标文件和库文件中的符号,解决外部符号的引用问题。
- 例如,在一个文件中调用另一个文件中定义的函数,链接器需要将调用与定义关联起来。
- 地址绑定:为每个符号(变量、函数等)分配实际内存地址。
- 例如,将
main
函数绑定到可执行文件中的特定内存地址。
- 例如,将
- 生成可执行文件:链接器生成最终的可执行文件,该文件包含了所有目标文件和库文件的机器码,以及最终的符号表和地址信息。
工具和命令:
gcc source.o -o executable
该命令生成可执行文件executable
。
5. 动态链接与加载(运行时)
除了静态链接生成的可执行文件外,还有动态链接和加载过程,这发生在程序运行时。
- 动态链接:在程序运行时,动态链接器(如
ld-linux.so
)将程序依赖的共享库动态加载到内存,并解析动态符号。 - 加载程序:操作系统加载器将可执行文件加载到内存中,并开始执行,从入口点(通常是
main
函数)开始运行程序。
总结
整个GCC编译过程可以总结为:
- 预处理:展开宏、包含头文件、删除注释,生成预处理后的代码。
- 编译:经过词法分析、语法分析、语义分析和优化,生成汇编代码。
- 汇编:将汇编代码转换为目标文件(机器码)。
- 链接:将多个目标文件和库文件链接成最终的可执行文件。
- 动态链接与加载(运行时):在程序执行时,动态链接库被加载,并解析动态符号,最终执行程序。
这个详细的流程展示了从源代码到最终可执行程序的每个细节步骤。每个阶段都有其特定的任务和工具,确保程序能够正确、高效地被执行。
const 修饰变量前后的内存变化
这个被问我是真不知道
c语言中const全局变量存储在只读数据段,编译期最初将其保存在符号表中,第一次使用时为其分配内存,在程序结束时释放。
而const局部变量(局部变量就是在函数中定义的一个const变量,)存储在栈中,代码块结束时释放。
在c语言中可以通过指针对const局部变量进行修改,而不可以对const全局变量进行修改。因为const全局变量是存储在只读数据段
而c++中,一个const不是必需创建内存空间,而在c中,一个const总是需要一块内存空间。
在c++中是否要为const全局变量分配内存空间,取决于这个const变量的用途,如果是充当着一个值替换(即就是将一个变量名替换为一个值),那么就不分配内存空间,不过当对这个const全局变量取地址或者使用extern时,会分配内存,存储在只读数据段。也是不能修改的。
c++中对于局部的const变量要区别对待:
对于基础数据类型,也就是const int a = 10这种,编译器会把它放到符号表中,不分配内存,当对其取地址时,会分配内存
对于基础数据类型,如果用一个变量初始化const变量,如果const int a = b,那么也是会给a分配内存
对于自定数据类型,比如类对象,那么也会分配内存。
c中const默认为外部连接,c++中const默认为内部连接.当c语言两个文件中都有const int a的时候,编译器会报重定义的错误。而在c++中,则不会,因为c++中的const默认是内部连接的。如果想让c++中的const具有外部连接,必须显示声明为: extern const int a = 10。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/woainilixuhao/article/details/86521357
类和对象在编译时如何被处理
盲区,完全就是盲区
单片机的内存管理
https://blog.csdn.net/weixin_42550185/article/details/131156144?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522172370982916800182791064%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=172370982916800182791064&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-131156144-null-null.142v100pc_search_result_base6&utm_term=%E5%8D%95%E7%89%87%E6%9C%BA%E7%9A%84%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86&spm=1018.2226.3001.4187
uart 通信协议
https://blog.csdn.net/wuyiyu_/article/details/137342569?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_utm_term~default-0-137342569-blog-132943014.235v43pc_blog_bottom_relevance_base5&spm=1001.2101.3001.4242.1&utm_relevant_index=3