Yanyg - SAN Software Engineer

Linux GCC编译 – 起步

目录

1 说明

本文最早发布在CSDN:https://blog.csdn.net/cppgp/article/details/3457718 本文适用于Linux下开发初学者。本文初步讲解在Linux下如何使用GCC编译程序、简单生成静态库及动态库。

2 安装

Debian系统安装:

~$ sudo apt-get install gcc

3 C编程中的文件后缀名介绍

  • .a 静态库(打包文件)
  • .c 未经过预处理的C源码
  • .h C头文件
  • .i 经过预处理的C源码
  • .o 编译之后产生的目标文件
  • .s 生成的汇编语言代码
  • .so 动态库(动态链接库)

解释: *.a是我们在编译过后用ar打包生成的静态库; .c一般使我们自己编辑的代码,是我们劳动的结晶; *.h一般是我们手工生成的接口文件,如果愿意,也可在.c完成后用GCC的选项-aux-info帮我们生成; *.i是经过预处理后的源码,是由GCC在选项-E编译下自动生成的文件; *.o是编译后产生的目标文件; *.s是GCC在选项-S编译下生成的汇编语言代码,对于性能要求很高的程序可以先生成汇编语言文件并对汇编做优化,然后用优化后的汇编生成目标文件并链接; *.so是动态库,通过GCC的-fpic -shared选项生成。

4 hello.c的编译过程

/*
 * hello.c
 */

#include <stdio.h>
int  main()
{
        printf("hello, world!/n");
        return 0;
}

4.1 直接生成可执行程序

$ gcc -o hello hello.c
$ ./hello
hello, world!

如下编译方式结果相同:
$ gcc hello.c -o hello
$ ./hello
hello, world!

如下编译方式有别于以上编译方案(具体查找ELF和a.out文件格式差别的网络资料,对于此处结果是无任何区别的):
$ gcc hello.c
$ ./a.out
hello, world!

4.2 生成预处理后的文件 hello.i

$ gcc -E hello.c -o hello.i
$ ls
a.out  hello  hello.c  hello.i
hello.i 就是新生成的文件

如下语句结果相同:
$ gcc -E -o hello.i hello.c

如果不设定输出文件,则打印到标准终端,此时我们可以用 less 查看:
$ gcc -E hello.c | less
# 1 "hello.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "hello.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 28 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 329 "/usr/include/features.h" 3 4
..............................

或者执行:
$ gcc -E hello.c -o hello.i
$ vi hello.i
  1 # 1 "hello.c"
  2 # 1 "<built-in>"
  3 # 1 "<command line>"
  4 # 1 "hello.c"
  5 # 1 "/usr/include/stdio.h" 1 3 4
  6 # 28 "/usr/include/stdio.h" 3 4
  7 # 1 "/usr/include/features.h" 1 3 4
  8 # 329 "/usr/include/features.h" 3 4

.......... <中间部分略> ..................

929 # 844 "/usr/include/stdio.h" 3 4
930
931 # 2 "hello.c" 2
932
933 int main()
934 {
935         printf("hello, world!/n");
936
937         return 0;
938 }

4.3 生成汇编语言文件 hello.s

$ gcc -S hello.c -o hello.s
$ ls
a.out  hello  hello.c  hello.i  hello.s
hello.s就是新生成的文件

如下语句结果相同:
$ gcc -S -o hello.s hello.c

如下语句结果相同:
$ gcc -S hello.c

也可以采用前一步骤产生的中间文件生成汇编文件:
$ gcc -S hello.i -o hello.s
$ gcc -S -o hello.s hello.i
$ gcc -S hello.i


生成的汇编部分代码如下:
$ vi hello.s
  1         .file   "hello.c"
  2         .section        .rodata
  3 .LC0:
  4         .string "hello, world!"
  5         .text
  6 .globl main
  7         .type   main, @function
  8 main:
  9         leal    4(%esp), %ecx
 10         andl    $-16, %esp
 11         pushl   -4(%ecx)
 12         pushl   %ebp

4.4 生成目标文件 hello.o

$ gcc -c hello.c -o hello.o
$ ls
a.out  hello  hello.c  hello.i  hello.o  hello.s
hello.o就是新生成的目标文件:

如下语句结果相同:
$ gcc -c -o hello.o hello.c

如下语句结果相同:
$ gcc -c hello.c

也可以采用前面步骤产生的中间文件hello.i或hello.s来生成目标文件:
$ gcc -c hello.i
$ gcc -c hello.s

我们可以用 objdump 查看 hello.o 的二进制码:
$ objdump -s hello.o

hello.o:     file format elf32-i386

Contents of section .text:
 0000 8d4c2404 83e4f0ff 71fc5589 e55183ec  .L$.....q.U..Q..
 0010 04c70424 00000000 e8fcffff ffb80000  ...$............
 0020 000083c4 04595d8d 61fcc3             .....Y].a..
Contents of section .rodata:
 0000 68656c6c 6f2c2077 6f726c64 2100      hello, world!.
Contents of section .comment:
 0000 00474343 3a202847 4e552920 342e312e  .GCC: (GNU) 4.1.
 0010 31203230 30373031 30352028 52656420  1 20070105 (Red
 0020 48617420 342e312e 312d3532 2900      Hat 4.1.1-52).

4.5 采用中间级文件生成可执行程序

$ gcc -o hello hello.i
$ ./hello
hello, world!

$ gcc -o hello hello.s
$ ./hello
hello, world!

$ gcc -o hello hello.o
$ ./hello
hello, world!

5 静态库的生成

linux下静态库的生成比较方便。在生成目标文件后用 ar 打包即可。在中大型项目中一个模块一般会做成一个静态库,以方便管理、提高编译、链接效率。

本小节的展示针对 main.c、func1.c、func2.c三个文件.

main.c:

/*
 * main.c
 */
#include <stdio.h>

extern int func1();
extern int func2();

int main()
{
        int i;

        i = func1();
        printf("func1 return = %d/n",i);

        i = func2();
        printf("func2 return = %d/n",i);

        return 0;
}

func1.c:

/*
 * func1.c
 */
int func1()
{
        return 100;
}

func2.c:

/*
 * func2.c
 */
int func2()
{
        return 200;
}

5.1 编译指令

$ gcc -c func1.c
$ gcc -c func2.c
$ ls
func1.c  func1.o  func2.c  func2.o  main.c

func1.o 和 func2.o 是我们生成的目标文件。打包指令如下:
$ ar -r libfunc.a func1.o func2.o

我们查看 libfunc.a 中的文件:
$ ar -t libfunc.a
func1.o
func2.o

现在用静态库和 main.c 共同生成目标程序:
$ gcc -o main main.c libfunc.a
$ ./main
func1 return = 100
func2 return = 200

6 动态库的生成

linux下动态库的生成通过GCC选项实现。案例程序和静态库中的相同。以下是操作指令:

首先我们生成目标文件,但是需要加编译器选项 -fpic 和链接器选项 -shared
$ gcc -fpic -c func1.c
$ gcc -fpic -c func2.c
$ gcc -shared -o libfunc.so func1.o func2.o
$ ls
func1.c  func1.o  func2.c  func2.o  libfunc.so  main.c
libfunc.so就是我们生成的目标动态库。我们用动态库和 main.c 生成目标程序:
$ gcc -o main main.c -L. -lfunc
注意,我们用 -L. -lfunc 作为编译选项。-L. 表从当前目录查找需要的动态库,-lfunc 是动态库的调用规则。
Linux系统下的动态库命名方式是 lib*.so,而在链接时表示位 -l* , *是自己起的库名。下面我们运行它:
$ ./main
./main: error while loading shared libraries: libfunc.so: cannot open shared object file: No such file or directory
提示一个错误,指示无法找到动态库。在linux下最方便的解决方案是拷贝libfunc.so到绝对目录 /lib 下。
但是只有超级用户才有这个权限。另外一个方案是更改环境变量 LD_LIBRARY_PATH。如下:
$ $ export LD_LIBRARY_PATH=`pwd`
$ ./main
func1 return = 100
func2 return = 200
运行成功。现在我们更改动态库的函数而不重新链接。如下:
更改 func1.c 为:
int func1()
{
        return 101;
}
更改 func2.c 为:
int func2()
{
        return 202;
}
重新生成库:
$ gcc -fpic -shared func1.c func2.c -o libfunc.so
$ ./main
func1 return = 101
func2 return = 202
可以看出,动态库已经更新了。

7 结束语

本文简单介绍了linux下如何使用gcc进行编译程序、以及简单的静态、动态库的生成。

静态库提供了一种打包管理方案,而动态库使程序局部更新成为了可能,更重要的是,当有多份实例存在时,动态库可减小内存的消耗(只占用一份代码空间)。

对本系列知识感兴趣者可继续跟踪阅读后续文章:库的版本管理、GCC的编译选项、Makefile 与自动化编译。