程序编译的基本流程

程序的编译过程,其实就是将我们编写的C源程序翻译成CPU能够 识别和运行的二进制机器指令的过程。关于C程序我们已经很熟悉了: 一个C程序主要由一行行C语言语句组成,不同的语句构成一个个代码块或函数,每个语句由C语言的关键字、运算符、预处理命令、用户定义的变量名、函数名等很多token构成。一个C语言项目通常由多个文件组成。

从C程序到可执行文件,整个编译过程并不是一气呵成、一步完成 的,而是环环相扣、多步执行的。如下图所示,程序的整个编译流程 主要分为以下几个阶段:预处理、编译、汇编、链接。每个阶段需要 调用不同的工具去完成,上一阶段的输出作为下一阶段的输入,步步推进。

在一个多文件的C项目中,编译器是以C源文件为单位进行编译的。在编译的不同阶段,编译程序(如gcc、arm-linux-gcc)会调用不 同的工具来完成不同阶段的任务。在编译器安装路径的bin目录下,你 会看到各种各样的编译工具,gcc在程序编译过程中会分别调用它们, 常见的工具有预处理器、编译器、汇编器、链接器。

● 预处理器:将源文件main.c经过预处理变为main.i。

● 编译器:将预处理后的main.i编译为汇编文件main.s。

● 汇编器:将汇编文件main.s编译为目标文件main.o。

● 链接器:将各个目标文件main.o、sub.o链接成可执行文件a.out。

最后生成的可执行文件a.out其实也是目标文件(object file),唯 一不同的是,a.out是一种可执行的目标文件。目标文件一般可以分为3 种。

● 可重定位的目标文件(relocatable files)。

● 可执行的目标文件(executable files)。

● 可被共享的目标文件(shared object files)。

汇编器生成的目标文件是可重定位的目标文件,是不可执行的, 需要链接器经过链接、重定位之后才能运行。可被共享的目标文件一 般以共享库的形式存在,在程序运行时需要动态加载到内存,跟应用 程序一起运行。

在 一 个 可 执 行 文 件 中 , 我 们 比 较 熟 悉 的 section 有.text、.data、.bss,就是我们常说的代码段、数据段、BSS段。C程序中定义的函数、变量、未初始化的全局变量经过编译后会放置在不同的段中:函数翻译成二进制指令放在代码段中,初始化的全局变量和 静态局部变量放在数据段中。BSS段比较特殊,一般来讲,未初始化的全局变量和静态变量会放置在BSS段中,但是因为它们未初始化,默认值全部是0,其实没有必要再单独开辟空间存储,为了节省存储空间, 所以在可执行文件中BSS段是不占用空间的。但是BSS段的大小、起始地址和各个变量的地址信息会分别保存在节头表section header table和 符号表.symtab里,当程序运行时,加载器会根据这些信息在内存中紧挨着数据段的后面为BSS段开辟一片存储空间,为各个变量分配存储单元。

如下图所示,就是将C程序中定义的函数、变量,挑挑拣拣、 加以分类,分别放置在可执行文件的代码段、数据段和BSS段中。程序中定义的一些字符串、printf函数打印的字符串常量则放置在只读数据段.rodata中。如果程序在编译时设置为debug模式,则可执行文件中还 会有一个专门的.debug section,用来保存可执行文件中每一条二进制指令对应的源码位置信息。根据这些信息,GDB调试器就可以支持源码级的单步调试,否则你单步执行的都是二进制指令,可读性不高,不方便调试。在最后环节,编译器还会在可执行文件中添加一些其他section,如.init section,这些代码来自C语言运行库的一些汇编代码, 用来初始化C程序运行所依赖的环境,如内存堆栈的初始化等。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: C语言编译出的二进制文件不能直接在Python中调用,需要通过某些方式间接地调用,常见的方式有以下几种: 1. 使用ctypes库,在Python代码中调用C语言写的动态链接库。 2. 使用Cython,将C语言代码编译为Cython代码,再通过Cython生成的Python模块调用C语言代码。 3. 使用SWIG,将C语言代码封装成Python模块。 详细实现方式可以参考相关教程或文档。 ### 回答2: 在Python中,可以通过使用`ctypes`模块来调用C编写的程序。`ctypes`是Python标准库提供的模块,它允许Python调用动态链接库(DLL)中的函数。 首先,需要确保C程序已经被编译成动态链接库(DLL)文件。可以使用常见的C编译器(如gcc)将C代码编译为共享对象(.so)文件。 在Python中,需要导入`ctypes`模块,并使用`cdll`加载动态链接库。以下是一个示例代码: ``` import ctypes # 加载动态链接库 lib = ctypes.CDLL('/path/to/c_program.so') # 调用C函数 lib.function_name(arguments) ``` 在代码中,`/path/to/c_program.so`是C程序编译后生成的动态链接库文件路径。`function_name`是C程序中的函数名,可以根据实际情况修改。 需要注意的是,调用C函数时需要指定参数类型和返回值类型。如果C函数接受和返回的是C语言中的基本类型(如整数、浮点数),可以使用`c_int`、`c_float`等类型。如果C函数接受和返回的是指针或结构体等复杂类型,需要在Python中定义对应的结构体或类型。 综上所述,通过`ctypes`模块可以方便地在Python中调用已经编译好的C程序。 ### 回答3: 在C程序编译之后,我们可以通过Python的ctypes模块来调用它。ctypes是Python的一个外部函数库的包装器,可以用于调用C函数库中的函数。 首先,我们需要将C程序编译成一个动态链接库文件(.so文件),这可以通过在C程序中使用编译器的特定选项来完成。 然后,在Python中使用ctypes模块加载编译后的动态链接库文件,并定义C函数的原型,以便Python能够正确地调用它。 下面是一个简单的例子,假设我们有一个名为“my_c_program.c”的C程序,其中包含一个名为“my_function”的函数,该函数接受一个整数参数并返回一个整数: // my_c_program.c文件 #include <stdio.h> int my_function(int num) { printf("Hello from C program! You passed %d\n", num); return num * 2; } 接下来,我们将使用gcc编译器将该C程序编译为动态链接库文件: $ gcc -shared -o my_c_program.so my_c_program.c 在Python中调用该动态链接库文件: import ctypes # 加载动态链接库 c_program = ctypes.CDLL('./my_c_program.so') # 定义my_function的参数类型和返回类型 c_program.my_function.argtypes = [ctypes.c_int] c_program.my_function.restype = ctypes.c_int # 调用C函数 result = c_program.my_function(42) print(f"Result from C function: {result}") 运行上述Python代码,它将输出以下内容: Hello from C program! You passed 42 Result from C function: 84 这说明我们成功地调用了编译后的C程序,并从Python中获取了返回的结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值