编译与链接 - 02

解决以下问题:

1.程序为什么要被编译器编译了之后才可以运行?

2.编译器在把C语言程序转换成可以执行的机器码的过程中做了什么?

3.最后编译出来的可执行文件里面是什么?除了机器码还有什么?他们怎么存放的?怎么组织的?

4.#include<stdio.h>是什么意思?把stdio.h包含进来意味着什么?C语言库又是什么?它怎么实现的?

5.不同编译器和不同平台的硬件平台,以及不同的操作系统,最终编译出来的结果是一样的吗?为什么?

6.hello world程序是怎么运行起来的?操作系统是怎么装它的?它从哪里开始执行,到哪里结束?main函数之前发生了什么?main函数结束以后又发生了什么?

7.如果没有操作系统,hello world可以运行吗?如果要在一台没有操作系统的机器上运行hello world需要什么?应该怎么实现?

8.printf是怎么实现的?它为什么可以有不定数量的参数?为什么它能够在终端上输出字符串?

9.hello world程序在运行时,它在内存中是什么样子的?


1.程序为什么要被编译器编译了之后才可以运行?

通过编译器将其变成二进制文件,使得计算机可以理解。同时,C 程序通常依赖标准库和操作系统服务,这些实现分散在库文件中。链接器负责把这些库与你自己的代码在地址层面连接起来,确保所有函数调用都能找到正确的实现。

2.编译器在把C语言程序转换成可以执行的机器码的过程中做了什么?

编译器把C程序转成可执行文件,要经过四个阶段:预处理(展开宏和头文件)→ 编译(转成汇编)→ 汇编(转成机器码)→ 链接(合并目标文件、解决符号、生成可执行文件)。

3.最后编译出来的可执行文件里面是什么?除了机器码还有什么?他们怎么存放的?怎么组织的?

**最终生成的可执行文件本质上是一个按特定格式组织的二进制文件。**以Linux为例,其格式为ELF。

ELF 文件中主要包括:

  1. ELF头

描述文件类型、目标架构、入口地址(Entry Point)等元信息。

  1. 程序头表

告诉操作系统如何把文件映射到内存(代码段、数据段等)。

  1. 段内容,常见有:

    1. 代码段(.text) :存放 CPU 可执行的机器指令(如 mainprintf的机器码)

    2. 数据段(.data):存放已初始化的全局变量和静态变量

    3. BSS 段(.bss):存放未初始化或初始化为 0 的全局变量(文件中不占空间,加载时分配)

    4. 只读数据段(.rodata) :如字符串常量 "hello world\n"

  2. 节头表与节

主要用于链接和调试(如 .symtab符号表、.strtab字符串表)。

组织方式:

  • ELF 文件在磁盘上按"头部 + 多个段/节"的方式线性存储;

  • 加载时,操作系统根据程序头表,把不同段映射到进程的虚拟地址空间;

  • ELF 中的入口地址(Entry Point)并不指向main,而是指向C运行库提供的入口函数(如_start);

  • 该入口函数负责初始化运行环境(栈、堆、I/O、全局构造等),然后调用 main

  • main返回后,入口函数执行清理并最终通过系统调用结束进程。

因此,ELF 文件不仅仅是机器码,还包含了代码、数据、地址信息和加载规则,操作系统正是依靠这些信息把程序正确地放进内存并运行起来。

4.#include<stdio.h>是什么意思?把stdio.h包含进来意味着什么?C语言库又是什么?它怎么实现的?

#include <stdio.h>把标准 I/O 的接口声明引入当前源文件;包含进来意味着让编译器知道这些函数是"存在的、长什么样";C语言库是一组由编译器或操作系统提供的、预先编译好的函数集合,用来实现C语言标准规定的各种功能(如 I/O、字符串处理、内存管理等);C 语言库本身也是用C写的,但它最终会调用操作系统提供的系统调用接口。

5.不同编译器和不同平台的硬件平台,以及不同的操作系统,最终编译出来的结果是一样的吗?为什么?

不一样。因为不同硬件的指令集不同、不同操作系统的 ABI 和系统调用不同、不同编译器的调用约定也不同,这些因素共同决定了最终生成的二进制程序是平台相关的。

6.hello world程序是怎么运行起来的?操作系统是怎么装它的?它从哪里开始执行,到哪里结束?main函数之前发生了什么?main函数结束以后又发生了什么?

复制代码
#include <stdio.h>

int main()
{
    printf("hello world.\n");
    return 0;
}

1.操作系统加载程序

  • 用户在 shell 中输入./hello

  • shell 调用fork + execve

  • 操作系统接收到命令,读取ELF文件,根据文件头信息将其加载到内存中,并分配进程资源。

  • OS跳转到程序的入口点(Entry Point),开始执行指令。

2.操作系统是怎么"装"它的?(加载过程)

操作系统加载程序的过程,其实就是解析ELF文件的过程:

  • 读取ELF头:确认文件格式合法,找到程序头表(Program Header Table)。

  • 创建虚拟地址空间:为进程划分内存区域(代码区、数据区、堆、栈)。

  • 映射段到内存 :根据程序头表,把ELF中的.text(代码)、.data(数据)等段,映射到物理内存中。注意:这里通常是按需加载(分页机制),不是一次性全部读入。

  • 设置运行环境:初始化栈指针(SP)、清零BSS段、打开stdin/stdout等。

3.它从哪里开始执行,到哪里结束?

  • 从哪里开始

    • 不是从 main开始

    • 是从ELF文件头中指定的Entry Point开始。

    • 这个地址通常指向C运行库提供的_start函数。

  • 到哪里结束

    • 程序结束的标志是进程退出。

    • 最终会执行到系统调用exit()_exit(),通知操作系统回收资源。

4.main 函数之前发生了什么?

从操作系统启动程序到main函数被调用前,C运行库会做大量初始化工作:

  1. _start 入口 :CPU开始执行 _start指令。

  2. 环境初始化

    1. 初始化C运行时环境(堆、全局变量等)。

    2. 解析命令行参数和环境变量。

  3. 全局构造

    1. 调用C++的全局/静态对象的构造函数。

    2. 执行.init.init_array段的代码。

  4. 注册退出处理

    1. 注册 atexit钩子,准备在 main结束后运行。
  5. 调用 main

    1. 最终,CRT 准备好参数,调用 main(argc, argv)

5.main 函数结束以后又发生了什么?

main函数执行 return后,并没有直接结束进程,CRT 还要做清理工作:

  1. 调用析构函数

    1. 执行C++全局/静态对象的析构函数。

    2. 执行.fini.fini_array段的清理代码。

  2. 运行退出回调

    1. 执行atexit注册的回调函数。
  3. 调用 exit()

    1. CRT调用标准库的exit()函数。
  4. 系统调用退出

    1. exit()刷新缓冲区、关闭文件流,最后触发系统调用sys_exit,通知内核。
  5. 资源回收

    1. 操作系统回收进程占用的内存、文件句柄、PCB(进程控制块)等资源。

7.如果没有操作系统,hello world可以运行吗?如果要在一台没有操作系统的机器上运行hello world需要什么?应该怎么实现?

可以运行。没有操作系统时,hello world 就是一个裸机程序:需要自己写启动代码、链接脚本,直接操作硬件输出,并把程序烧进 ROM 从入口地址开始执行。

8.printf是怎么实现的?它为什么可以有不定数量的参数?为什么它能够在终端上输出字符串?

C 语言专门给了三个工具来做这件事:

  • va_list← 一个指针

  • va_start← 指向第一个可变参数

  • va_arg← 取下一个参数

你不用记宏怎么写,只需要记住:

printf 是通过"栈 + 指针偏移"把参数一个一个取出来的

printf 通过格式字符串解析栈中的不定参数,并通过系统调用或硬件操作将结果输出到终端。

9.hello world程序在运行时,它在内存中是什么样子的?

hello world 运行时,操作系统根据 ELF 文件在进程的虚拟地址空间中建立起代码段、数据段、堆和栈,并通过页表将这些虚拟地址映射到物理内存,使程序在统一的地址模型中执行。

相关推荐
巴糖1 小时前
Embedding了解一些
算法
战族狼魂1 小时前
广东备案大模型超百款
人工智能·算法·大模型·大语言模型
千桐科技1 小时前
🚀 qModel 算法模型平台v1.2.0 开源版发布!Python 模型接入链路全面升级,告别“能跑但上不了线”的尴尬
后端·算法·llm
刘沅2 小时前
LeetCode 93.复原IP地址
算法·面试
阿里技术3 小时前
Agent 评测:方法论与体系设计
大数据·人工智能·算法
cxr8283 小时前
大语言模型上下文缓存命中率测试全场景清单
人工智能·python·算法·缓存·语言模型·自然语言处理·llm
noipp3 小时前
推荐题目:洛谷 P13554 【MX-X15-T1】奶龙龙
c语言·数据结构·c++·算法·编程·洛谷
念风3 小时前
一阶低通滤波器系数的两种计算方法
算法
geovindu4 小时前
go: Enumeration Algorithm
开发语言·后端·算法·golang·枚举算法