C语言编译过程详解

编译过程

C语言是一种编译型语言,需要把源文件进行编译之后才能运行,它的编译过程如下:

  1. 预处理:展开头文件、宏替换,去掉注释,条件编译
  2. 编译:检查语法,生成汇编
  3. 汇编:把生成的汇编文件汇编成机器码
  4. 链接: 链接到一起生成可执行程序 a.out

创建hello.c文件,用于测试:

#include <stdio.h>
#define PI 3.14
int main(){
    printf("Hello C %f\n",PI);
    return 0;
}

预处理

预处理会做展开头文件/宏替换/去掉注释/条件编译操作, 这一步主要是做一些处理,比如把我们include的一些文件都包含进来。而且把所有的预处理命令都执行掉。比如 我们常见的#define PI 3.14

执行下面的命令进行预处理:

gcc -E hello.c -o hello.i

-E :Preprocess only; do not compile, assemble or link.
可以看一下hello.i文件:


extern int sys_nerr;
extern const char *const sys_errlist[];
# 854 "/usr/include/stdio.h" 2 3 4
extern int fileno (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern int fileno_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
# 873 "/usr/include/stdio.h" 3 4
extern FILE *popen (const char *__command, const char *__modes) ;
extern int pclose (FILE *__stream);
extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
# 913 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 943 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 3 "hello.c"
int main(){
 printf("Hello C %f\n",3.14);
 return 0;
}

可以看到main函数上面把原来include<stdio.h>文件给包含了进来,且我们定义的宏#define PI 3.14也被进行了预处理

编译(生成汇编)

这一步会先检查语法,然后生成汇编。
命令如下:

gcc -S  hello.c -o hello.s

-S: Compile only; do not assemble or link.
生成的hello.s汇编文件如下:

    .file   "hello.c"
    .text
    .section    .rodata
.LC1:
    .string "Hello C %f\n"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movq    .LC0(%rip), %rax
    movq    %rax, %xmm0
    movl    $.LC1, %edi
    movl    $1, %eax
    call    printf
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .section    .rodata
    .align 8
.LC0:
    .long   1374389535
    .long   1074339512
    .ident  "GCC: (GNU) 9.3.1 20200408 (Red Hat 9.3.1-2)"
    .section    .note.GNU-stack,"",@progbits

汇编

把汇编代码汇编成机器码
命令如下:

 gcc -c hello.s -o hello.o

-c Compile and assemble, but do not link.
这一步生成机器码,自己可以查看下是一堆二进制数据。

链接

将二进制机器码,转换成二进制语言的可执行程序

gcc hello.o -o a.out

上面所有的步骤都可以一条命令执行完成:

  gcc hello.c -o a.out

这里贴上一段gcc/g++编译器的描述:

C 和 C++ 编译器 是 集成的. 他们 都要 用 四个步骤 中的 一个 或 多个 处理 输入文件: 预处理(preprocessing), 编译(compilation),  汇编(assem‐
       bly) 和 连接(linking). 源文件后缀名 标识 源文件 的 语言, 但是 对 编译器 来说, 后缀名 控制着 缺省设定:

       gcc    认为 预处理后的 文件 (.i) 是 C 文件, 并且 设定 C 形式 的 连接.

       g++    认为 预处理后的 文件 (.i) 是 C++ 文件, 并且 设定 C++ 形式 的 连接.

       源文件后缀名 指出 语言种类 以及 后期 的 操作:

       .c    C 源程序; 预处理, 编译, 汇编
       .C    C++ 源程序; 预处理, 编译, 汇编
       .cc   C++ 源程序; 预处理, 编译, 汇编
       .cxx  C++ 源程序; 预处理, 编译, 汇编
       .m    Objective-C 源程序; 预处理, 编译, 汇编
       .i    预处理后的 C 文件; 编译, 汇编
       .ii   预处理后的 C++ 文件; 编译, 汇编
       .s    汇编语言源程序; 汇编
       .S    汇编语言源程序; 预处理, 汇编
       .h    预处理器文件; 通常 不出现在 命令行 上

       其他 后缀名 的 文件 被传递 给 连接器(linker). 通常 包括:

       .o    目标文件 (Object file)
       .a    归档库文件 (Archive file)

       除非 使用了 -c, -S, 或 -E 选项 (或者 编译错误 阻止了 完整 的 过程), 否则 连接 总是 最后的步骤.  在 连接阶段 中, 所有 对应于 源程序 的 .o
       文件, -l 库文件, 无法 识别 的 文件名 (包括 指定的 .o 目标文件 和 .a 库文件) 按 命令行中 的 顺序 传递给 连接器.

制作静态链接库

先创建c文件目录,目录结构如下

-CLearn
	-lib  存放我们生成的静态链接库文件
	-include 存放我们的库文件源码和头文件
		-selflib.h 头文件
		-selflib.c 库文件源码
	-hello.c 示例程序

selflib.h内容:

#ifndef __SELFLIB_H__
#define __SELFLIB_H__
int a=20;
extern void test();
#endif

selflib.c内容:

#include <stdio.h>
int test(){
    printf("test\n");
    return 0;
}

hello.c内容:

#include <stdio.h>
#include "libself.h"
#define PI 3.14
int main(){
    printf("hello C %2f \n",PI);
    printf("a:%d \n",a);
    test();
    return 0;
}

编译libself.c

gcc -c include/libself.c -o libself.o

-c:仅编译,不执行链接,必须加这个参数
把libself.o转为静态链接库

ar -r lib/libself.a include/libself.o

说明:
-r: 把目标文件(libself.o)插入到库文件(libself.a)中

使用库文件编译hello.c

gcc hello.c -I include -lself -L ./lib -o hello

说明:
-I(大写i) 头文件搜索目录
-l(小写L):指定库文件名,注意-l后面直接添加库名省去“lib”和“.so”或“.a”
-L:静态链接库或者动态链接库搜索目录

制作动态链接库

编译libself.c

gcc -c include/libself.c -fPIC -o include/libself.o

-fPIC : 生成 位置无关目标码. 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的

生成共享库

 gcc -shared include/libself.o -o lib/libself.so

-shared 生成 一个 共享目标文件, 他 可以 和 其他 目标文件 连接 产生 可执行文件

将hello.c编译链接为可执行程序

 gcc hello.c lib/libself.so -I include -o hello 
 或者
 gcc hello.c -lself -L ./lib -I include -o hello

上面第二个使用方法和连接静态库是一样的。也就是说-l(小写L)选项也会查找.so文件,如果.so和.a文件同时存在,则优先连接.so文件

编译链接生成的hello文件还不能执行,上面我们使用的链接库是连接时用的链接库地址,真正执行时系统是不知道我们的链接库地址的,所以要把so文件copy到/usr/lib或者/usr/local/lib下,也可以在LD_LIBRARY_PATH环境变量上追加我们的库路径

cp lib/libself.so /usr/local/lib/
或者
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./lib
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值