0
点赞
收藏
分享

微信扫一扫

【C primer Plus】执行过程、.c 、.h ?


文章目录

  • ​​目标代码、可执行文件和库​​
  • ​​编译原理​​
  • ​​头文件中声明变量、宏、结构体而 C 文件中定义变量、函数实现​​
  • ​​举例​​

​​GCC 参数详解​​

1、预处理,生成 .i 的文件[预处理器cpp]
2、将预处理后的文件转换成汇编语言, 生成文件 .s [编译器ccl]
3、有汇编变为目标代码(机器代码)生成 .o 的文件[汇编器as]
4、连接目标代码, 生成可执行程序 [链接器ld]

在 Linux 中,C 源文件的后缀名为 .c,而 C++ 源文件的后缀名为 .C 或 .cpp。

【C primer Plus】执行过程、.c 、.h ?_linux

选项

功能描述

-o

生成目标文件,可以是 .i、.s、.o 文件

-E

只运行C预编译器

-c

通知gcc取消链接,只编译生成目标文件,不做最后的链接

-Wall

生成所有警告信息

-w

不生成任何警告信息

-I

指定头文件的目录路径

-L

指定库文件的目录路径

-static

链接成静态库

-g

包含调试信息

-v

输出编译过程中的命令行和编译器版本等信息

-Werror

把所有警告信息转换成错误信息,并在警告发生时终止编译

-O0

关闭所有优化选项

-O1

最基本的优化等级

-O2

推荐使用的优化等级,编译器会尝试提高代码性能,而不会占用大量存储空间和话费大量编译时间

-O3

最高优化等级,会延长编译时间

目标代码、可执行文件和库

  1. 编译器把源代码转换成中间代码(即目标代码obj,二进制,其缺少不同平台所需要的启动代码(程序和操作系统之间的接口)和库函数);
  2. 链接器中间代码其它代码(预编译的库代码和启动代码)合并,生成可执行文件exe

目标代码(由编译器翻译源代码实现)+ 启动代码(为了匹配不同操作系统,由链接器实现)+ 库代码(链接器实现,复制拷贝额外的帮助代码 = 可执行文件

编译原理

  1. 预处理阶段

得到原生程序(头文件被替换为相应代码,printf 函数没有替换)
找到 c 中的头文件,并处理头文件中含有的宏、变量、函数声明、嵌套的头文件、依赖关系、宏替换、检查重复定义与声明,将所有的内容扫描到当前 C 文件中,形成中间文件。(文件拼接过程,动态编译)

因为 #include “xx.h” 这个宏其实际意思就是把当前这一行删掉,把 xx.h 中的内容原封不动的插入在当前行的位置。由于想写这些函数声明的地方非常多(每一个调用 xx.h 中函数的地方,都要在使用前声明一下子),所以用 #include “xx.h” 这个宏就简化了许多行代码–让预处理器自己替换好了。

  1. 编译阶段

按照一定标准将中间文件转换为二进制目标文件
将中间文件编译为二进制代码,按照特定目标文件格式生成目标文件,这个格式中目标文件的各个全局变量,函数的符号描述,都会按照标准组织成一个目标文件(obj,二进制)。

静态编译:

在这种方式下,我们所要做的,就是写出包含函数,类等等声明的头文件(a.h,b.h,…),以及他们对应的实现文件(a.cpp,b.cpp,…),编译程序会将其编译为静态的库文件(a.lib,b.lib,…)。在随后的代码重用过程中,我们只需要提供相应的头文件(.h)和相应的库文件(.lib),就可以使用过去的代码了。

  1. 通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。编译器会从库中提取相应的代码。
  2. 头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。


  1. 链接阶段

重定位各个目标文件的函数,变量。将目标文件中的二进制码和一些额外的代码按一定的规范整合成为一个可执行文件。

在该阶段会关心 # include 中函数的具体实现。

头文件中声明变量、宏、结构体而 C 文件中定义变量、函数实现

  1. 如果在头文件中定义一个函数体,有没有将其设置为局部函数,在链接时,发现多个函数就会报错。
  2. 如果在头文件中定义全局变量,并赋予初值,那么在多个引用此头文件的 C 文件中同样存在相同变量名的拷贝,存在于 DATA 段中,在链接阶段,在 DATA 段中存在多个相同的变量。而如果没有赋予初值,那么该变量就会被放在 BSS 段,链接器会对 BSS 段的多个同名变量仅分配一个存储空间。
  3. 将许多 C 文件都会用到的数据结构集中管理。
  4. 一方面可以将实现过程分装起来,另一方面可以提供头文件作为调用接口的参考。

main函数为标准C/C++的程序入口,编译器会先找到该函数所在的文件。

举例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PAGE_SIZE 4096
#define MAX_SIZE 100*PAGE_SIZE

int main()
{
char* buf = (char*)malloc(MAX_SIZE);
memset(buf ,0, MAX_SIZE);
printf("buffer address=0x%p\n", buf);
free(buf);
return 0;
}
  1. 预处理:​​aarch64-linux-gnu-gcc -E test.c -o test.i​​​​-E​​ 只运行C预编译器。

test.i:

...
extern char *__stpcpy (char *__restrict __dest, const char *__restrict __src)
__attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
extern char *stpcpy (char *__restrict __dest, const char *__restrict __src)
__attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));



extern char *__stpncpy (char *__restrict __dest,
const char *__restrict __src, size_t __n)
__attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
extern char *stpncpy (char *__restrict __dest,
const char *__restrict __src, size_t __n)
__attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
# 499 "/usr/include/string.h" 3 4

# 4 "test.c" 2





# 8 "test.c"
int main()
{
char* buf = (char*)malloc(100*4096);
memset(buf ,0, 100*4096);
printf("buffer address=0x%p\n", buf);
free(buf);
return 0;
}
  1. 编译:​​aarch64-linux-gnu-gcc -S test.i -o test.s​​​​-S​​ 只激活预处理和编译,就是指把文件编译成为汇编代码。

test.s

.arch armv8-a
.file "test.c"
.text
.section .rodata
.align 3
.LC0:
.string "buffer address=0x%p\n"
.text
.align 2
.global main
.type main, %function
main:
.LFB6:
.cfi_startproc
stp x29, x30, [sp, -32]!
.cfi_def_cfa_offset 32
.cfi_offset 29, -32
.cfi_offset 30, -24
mov x29, sp
mov x0, 16384
movk x0, 0x6, lsl 16
bl malloc
str x0, [sp, 24]
mov x2, 16384
movk x2, 0x6, lsl 16
mov w1, 0
ldr x0, [sp, 24]
bl memset
ldr x1, [sp, 24]
adrp x0, .LC0
add x0, x0, :lo12:.LC0
bl printf
ldr x0, [sp, 24]
bl free
mov w0, 0
ldp x29, x30, [sp], 32
.cfi_restore 30
.cfi_restore 29
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE6:
.size main, .-main
.ident "GCC: (Debian 9.2.1-28) 9.2.1 20200203"
.section .note.GNU-stack,"",@progbits
  1. 汇编​​aarch64-linux-gnu-gcc -c test.s -o test.o​​​​-c​​只激活预处理,编译,和汇编,也就是他只把程序做成obj文件
  2. 链接。​​aarch64-linux-gnu-gcc test.o -o test --static​​​。​​--static​​​ 此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么动态连接库,就可以运行。
    默认链接 C 语言标准库(libc),Linux 内核中的库分成两大类:动态链接库和静态链接库。GCC 优先使用动态链接库。使用 --static 参数来让 test 程序静态链接 C 语言标准库。
benshushu:linuxProject# ls -l
total 628
-rwxr-xr-x 1 root root 576808 Aug 29 08:17 test
-rw-rw-r-- 1 benshushu benshushu 271 Aug 29 08:07 test.c
-rw-r--r-- 1 root root 52343 Aug 29 08:10 test.i
-rw-r--r-- 1 root root 1824 Aug 29 08:15 test.o
-rw-r--r-- 1 root root 756 Aug 29 08:13 test.s

以 ARM64 GCC交叉工具链为例。

  • C 语言标准库的动态库地址为 /usr/arm-linux-gnueabi/lib/libc.so.6
  • 静态库地址:/usr/aarch64-linux-gnu/lib/libc.a


举报

相关推荐

C++ Primer Plus

读《C Primer Plus》

C++ Primer Plus 0001

C Primer Plus 编程练习10.12

【C++ Primer Plus习题】16.2

【C++ Primer Plus习题】12.4

C Primer Plus 第3章(数据和C)

C Primer Plus 8-10章

0 条评论