C程序预处理、编译、汇编、链接简介

从下面这个最简单的例子开始:

qingsong@ubuntu:~/test$ ls
hello.c

qingsong@ubuntu:~/test$ cat hello.c

#include <stdio.h>

#define PI 3.14159
#define RADIUS 4

int main(void)
{
        printf("Hello,world\n");

        //Calculate
        printf("Circumference is: %f\n", 2 * PI * RADIUS);
}

qingsong@ubuntu:~/test$ gcc hello.c 
qingsong@ubuntu:~/test$ ls
a.out  hello.c
qingsong@ubuntu:~/test$ ./a.out 
Hello,world
Circumference is: 25.132720

qingsong@ubuntu:~/test$ rm a.out

其实,上面的步骤包括了预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)和链接(Linking)这4个步骤,作用分别如下:
1. 预处理(Preprocessing): 主要处理文本中以#开头的部分,比如删除所有的#define并展开宏定义、删除所有注释、添加行号和文件名标识等。可以使用gcc的-E选项对源文件只进行预处理,示例如下:
qingsong@ubuntu:~/test$ gcc -E hello.c -o hello.i
qingsong@ubuntu:~/test$ ls
hello.c  hello.i

qingsong@ubuntu:~/test$ more hello.i

..
typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
..
extern void setlinebuf (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));

extern int fprintf (FILE *__restrict __stream,
      const char *__restrict __format, ...);

extern int printf (const char *__restrict __format, ...);

extern int puts (const char *__s);

extern int fseek (FILE *__stream, long int __off, int __whence);

# 6 "hello.c"
int main(void)
{
 printf("Hello,world\n");


 printf("Circumference is: %f\n", 2 * 3.14159 * 4);
}
能够看到经过预处理之后的文件 hello.i中内容如上面所示

2. 编译(Compilation):
编译的目的是将预处理之后的文件转换为汇编代码,当然,这中间经过了词法分析、语法分析、语义分析及优化,示例如下:
qingsong@ubuntu:~/test$  gcc -S hello.i -o hello.s
qingsong@ubuntu:~/test$ ls
hello.c  hello.i  hello.s

qingsong@ubuntu:~/test$ more hello.s

        .file   "hello.c"
        .text
        .section        .rodata
.LC0:
        .string "Hello,world"
.LC2:
        .string "Circumference is: %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
        subq    $16, %rsp
        leaq    .LC0(%rip), %rdi
        call    puts@PLT
        movq    .LC1(%rip), %rax
        movq    %rax, -8(%rbp)
        movsd   -8(%rbp), %xmm0
        leaq    .LC2(%rip), %rdi
        movl    $1, %eax
        call    printf@PLT
        movl    $0, %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .section        .rodata
        .align 8
.LC1:
        .long   4028335726
        .long   1077486073
        .ident  "GCC: (Ubuntu 7.3.0-16ubuntu3) 7.3.0"
        .section        .note.GNU-stack,"",@progbits
可以看到,汇编语言仍然是可读的

3. 汇编(Assembly):
汇编的目的是将汇编转换为机器语言(二进制),示例如下:
qingsong@ubuntu:~/test$ gcc -c hello.s -o hello.o
qingsong@ubuntu:~/test$ ls
hello.c  hello.i  hello.o  hello.s
也可以直接将源代码一步转换为机器语言:
qingsong@ubuntu:~/test$ rm hello.o
qingsong@ubuntu:~/test$ gcc -c hello.c -o hello.o
qingsong@ubuntu:~/test$ ls
hello.c  hello.i  hello.o  hello.s
这里的hello.o文件就是目标文件了

4. 链接(Linking):
大型的软件可能有多个模块,每个都有目标文件生成,这时候就需要链接器将这些不同的目标文件链接起来,生成单个的可执行文件。在上面的例子中只有一个目标文件,但仍然需要链接库文件(库其实就是一组目标文件的包,就是最常见的代码编译成目标文件后打包)
qingsong@ubuntu:~/test$ gcc hello.o -o a.out
qingsong@ubuntu:~/test$ ls
a.out  hello.c  hello.i  hello.o  hello.s
qingsong@ubuntu:~/test$ ./a.out 
Hello,world
Circumference is: 25.132720
qingsong@ubuntu:~/test$

这里再举一个例子:
qingsong@ubuntu:~/test2$ ls
main.c  mymath.h  mymul.c  myplus.c
qingsong@ubuntu:~/test2$ cat main.c
#include <stdio.h>
#include "mymath.h"

int main(void)
{
        int x = 3, y = 4;

        printf("x + y is:%d\n", myplus(x,y));
        printf("x * y is:%d\n", mymul(x,y));

        return 0;
}
qingsong@ubuntu:~/test2$ cat mymath.h 
extern int myplus(int x, int y);
extern int mymul(int x, int y);
qingsong@ubuntu:~/test2$ cat mymul.c 
int mymul(int x, int y)
{
        return x * y;
}
qingsong@ubuntu:~/test2$ cat myplus.c 
int myplus(int x, int y)
{
        return x + y;
}
qingsong@ubuntu:~/test2$ gcc -c myplus.c -o myplus.o
qingsong@ubuntu:~/test2$ gcc -c mymul.c -o mymul.o
qingsong@ubuntu:~/test2$ gcc -c main.c -o main.o
qingsong@ubuntu:~/test2$ gcc myplus.o mymul.o main.o -o mycalc.exe
qingsong@ubuntu:~/test2$ ./mycalc.exe 
x + y is:7
x * y is:12
qingsong@ubuntu:~/test2$


上面的例子中,如果每次都要运行那么多gcc命令,会特别麻烦,可以编写一个Makefile文件,将各个组件的依赖关系及生成方式写进去:

qingsong@ubuntu:~/test2$ rm *.o
qingsong@ubuntu:~/test2$ rm *.exe
qingsong@ubuntu:~/test2$ ls
main.c  mymath.h  mymul.c  myplus.c
qingsong@ubuntu:~/test2$ vi Makefile 
mycalc.exe:myplus.o mymul.o main.o
        gcc myplus.o mymul.o main.o -o mycalc.exe
myplus.o:myplus.c
        gcc -c myplus.c -o myplus.o
mymul.o:mymul.c
        gcc -c mymul.c -o mymul.o
main.o:main.c
        gcc -c main.c -o main.o
clean:
        rm *.o
        rm mycalc.exe
qingsong@ubuntu:~/test2$ make
gcc -c myplus.c -o myplus.o
gcc -c mymul.c -o mymul.o
gcc -c main.c -o main.o
gcc myplus.o mymul.o main.o -o mycalc.exe
qingsong@ubuntu:~/test2$ ls
main.c  main.o  Makefile  mycalc.exe  mymath.h  mymul.c  mymul.o  myplus.c  myplus.o
qingsong@ubuntu:~/test2$ ./mycalc.exe 
x + y is:7
x * y is:12
qingsong@ubuntu:~/test2$ make clean
rm *.o
rm mycalc.exe
qingsong@ubuntu:~/test2$ ls
main.c  Makefile  mymath.h  mymul.c  myplus.c
qingsong@ubuntu:~/test2$ 

qingsong@ubuntu:~/test2$ make
gcc -c myplus.c -o myplus.o
gcc -c mymul.c -o mymul.o
gcc -c main.c -o main.o
gcc myplus.o mymul.o main.o -o mycalc.exe
qingsong@ubuntu:~/test2$ vi myplus.c 
int myplus(int x, int y)
{
        int z;
        
        z = x + y;

        return z;
}
qingsong@ubuntu:~/test2$ make
gcc -c myplus.c -o myplus.o
gcc myplus.o mymul.o main.o -o mycalc.exe
qingsong@ubuntu:~/test2$ 

可以看到,由于只修改了myplus.c,所以只对这个文件进行了重新编译。
https://www.cnblogs.com/zhanghongfeng/p/7813172.html
最后的clean操作清除执行过程中产生的临时文件。当用make命令执行的时候,clean下的命令不会执行,要以make clean方式单独执行。执行后,所有*.o和main都被删除。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值