使用gcc编译C程序

Posted addsomesugar

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用gcc编译C程序相关的知识,希望对你有一定的参考价值。

GCC 全称"GNU C Compiler",不过自从面世后,增加了多种语言的支持,不过用的最多的是还是编译C或C++程序(另外有个工具叫做G++)。GCC是一种多目标编译器,通过可交互的后端处理器,为多种计算机架构生成可执行程序。

话说回来,什么是编译器呢?

编译器并不是一个单一的程序,它们通常由六七个稍小的程序组成,这些程序由一个叫做“编译器驱动器(Compiler driver)"的控制程序调用。

编译器一般由以下几部分组成:

  • 预处理器(preprocessor)
  • 语法和语义检查器(syntactic and semantic checker)
  • 代码生成器(code generator)
  • 汇编程序(assembler)
  • 优化器(optimizer)
  • 链接器(linker)

链接器确认main函数的初始进入点(程序开始执行的地方),把符号引用(symbolic reference)绑定到内存地址,把所有的目标文件集中到一起,再加上库文件,从而产生可执行文件。

本文主要介绍使用GCC编译C程序,不过GCC不支持C语言的许多“方言”,目前一般使用命令行参数 -std=c99 指定编译器支持C99标准。GCC对C11的标准支持是不完整的,尤其是涉及定义在头文件<threads.h>中的多线程函数。这是因为GCC的C链接库长期以来支持POSIX标准的多线程功能,与C11中的多线程功能非常相似。

一般gcc程序会链接到cc上:

$ ls -alF /usr/bin/cc
lrwxrwxrwx 1 root root 20 1月   4 09:20 /usr/bin/cc -> /etc/alternatives/cc*$ ls -alF /etc/alternatives/cclrwxrwxrwx 1 root root 12 1月   4 09:20 /etc/alternatives/cc -> /usr/bin/gcc*

可以通过--version选项查看gcc版本:

$ gcc --version
gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

一、 逐步实现使用gcc编译c程序

下面介绍gcc命令各个选项,控制编译过程的各个阶段:预处理(preprocessing)、编译(compiler)、汇编(assembling)和链接(linking)。也可以调用独立的工具,如c语言的预处理器cpp、汇编器as和链接器ld,来独立执行对应的步骤。

1.1 预处理

以下面代码为例:

#include <stdio.h>

int main(int argc, char *argv[])
{
    /*print hello world*/
    printf("hello world!
");
    return 0;
}

将源代码交给编译器之前,由预处理器执行命令,展开源代码中的宏。-E 选项指示gcc在预处理器完毕后即可停止。预处理器的输出会被导入到标准输出流,也可以通过 -o 选项把它输出到文件:

$ gcc -E -o helloworld.i helloworld.c
$ cat helloworld.i

...

# 3 "helloworld.c"
int main(int argc, char *argv[])
{

 printf("hello world!
");
 return 0;
}

一般来说,预编译器会将源代码中的注释去掉,可以使用 -C 选项阻止预编译器删除注释。

$ gcc -E -C -o helloworld.i helloworld.c
$ cat helloworld.i

...

# 3 "helloworld.c"
int main(int argc, char *argv[])
{
 /*print hello world*/
 printf("hello world!
");
 return 0;
}

关于gcc预处理器的常用选项:

选项 含义
-Dname[=definition] 配合 #ifdef name 实现条件编译,这里的name必须是源代码中没有#define过的;若没有指定definition,则该name被定义为1.
-Uname 若源代码中define过符号name,则“取消”该符号的定义,-U和-D会依据在命令中的先后顺序进行处理。
-Idirectory[: directory[...]] 指定系统标准include目录外的头文件搜索目录。
-iquote directory[:directory[...]] 仅指定include中采用引号而非尖括号的头文件搜索目录。
-isystem directory[:directory[...]] 优先于系统include目录的搜索目录。在目录开头位置的等号,被视作系统根目录的占位符,可以使用--sysroot或-isysroot选项来修改它。
-isysroot directory 和 --sysroot directory -isysroot指定包含目录的系统根目录,--sysroot指定链接库的系统根目录。例如,编译器指定包含目录为/usr/include及其子目录,则该选项将引导到directory/usr/include下进行搜索。
-I- 新版gcc中被-iquote代替。-I-左边加上-I目录,只对#include中引号包含的头文件进行搜索。-I-右边加上-I目录,对#include中引号和尖括号的头文件进行搜索。而且,如果命令中出现了-I-,则源文件本身的目录不再被搜索。

对于头文件的搜索目录,通常的顺序是:

  1. 包含指定的源文件目录(#include引号包含的文件名);
  2. 采用-iquote选项指定的目录;
  3. -I选项指定的目录;
  4. 采用环境变量CPATH指定的目录;
  5. 采用-isystem指定的目录;
  6. 采用环境变量C_INCLUDE_PATH指定的目录;
  7. 系统默认include目录。

1.2 编译

编译器的核心是把C程序翻译成汇编语言,汇编语言是人可以读懂的语言,也是更接近机器码的语言。各种CPU架构都有不同的汇编语言。

使用-S选项,让编译程序生成汇编语言输出后立刻停止。如果没有指定输出文件名,那么采用-S选项的gcc编译过程会为每个编译输出文件生成以.s作为后缀的汇编语言文件。并且使用-fverbose-asm选项,将c语言中的变量名称作为汇编语言中的注释。

$ gcc -S -fverbose-asm helloworld.c # 生成helloworld.s

1.3 汇编

每个机器架构都有自己的汇编语言,gcc调用宿主系统的汇编器,将汇编程序翻译成可执行的二进制代码。

该结果是个对象文件(object file),它包含机器码以执行对应的源文件中所定义的函数,它同时包含一个符号表,描述了源文件中具有外部链接的所有对象。

使用-c选项指示gcc不会链接该程序,但会对每个输入文件生成对象文件,其文件名后缀是.o

$ gcc -c helloworld.c # 生成helloworld.o

使用-Wa选项把命令行选项传递给汇编器:

$ gcc -v -o hello -Wa,-as=hello.sym,-L helloworld.c  # 生成hello.sym 和 hello可执行程序

这里的-as=hello.sym表示在单独一个列表中输出模块的符号表,-L表示在符号表中包含本地符号表。

1.4 链接

链接器把多个二进制文件链接成一个可执行文件。

标准库的大部分函数通常放在libc.a文件中,或者放在libc.so的动态共享链接文件中。这些链接库一般位于/lib/usr/lib中.

以下列代码为例:

/*helloworld.h*/
void hello();
/*helloworld.c*/
#include "helloworld.h"
void hello() {printf("hello world!
");}
/*main.c*/
#include "helloworld.h"
void main(int argc, char *argv[]) {hello();}

创建动态链接库和静态链接库

说到链接,先讲一讲动态链接文件和静态链接文件。

如果想创建一个共享对象文件,可以使用gcc的-shared选项。输入文件必须是一个已存在的对象文件。

$ gcc -c helloworld.c  # 生成.o文件
$ gcc -shared -o libhelloworld.so helloworld.o  # 生成.so文件
$ ar -rc libhelloworld.a helloworld.o  # 生成.a文件

链接

通常,gcc会自动在标准库目录中搜索文件,例如/usr/lib。在-l选项后紧跟库的名字:

$ gcc -o hello main.c -lhelloworld  # 若libhelloworld.a在标准库目录下,则自动链接,此处会出错

关于链接搜索路径以外的链接库,有三种方式:

$ gcc -o hello main.c libhelloworld.a  # 把链接库作为一般对象文件
$ gcc -o hello main.c -L./ -lhelloworld  # 使用-L选项增加库目录的搜索路径
$ gcc -o hello main.c -lhelloworld  # 需要将链接库的目录加到环境变量LIBRARYPATH中

关于动态库的链接,可以将.so文件拷贝到/usr/lib文件夹下,使用-l选项链接,也可以直接指定lib*.so的文件名:

# 先将libhelloworld.so拷贝到/usr/lib目录下
$ gcc -o hello main.c -lhelloworld
$ gcc -o hello main.c libhelloworld.so  # 二者选其一

如果系统支持共享链接库,但是不想使用时。可以使用:

$ gcc -static -o hello main.c -lhelloworld

两个重要选项:

  • 使用-save-temps选项生成所有中间文件,与对应的源文件具有相同的文件名,但文件扩展名分别为.i.s.o
  • 使用-fsyntax-only选项,就不会执行预处理、编译、汇编和链接。只会测试输入文件的语法是否正确。

二、 编译器警告

gcc可以对其提供的警告信息进行充分控制。

如果不希望区分警告和错误,可以使用-Werror,让gcc遇到任何警告时都停止编译。

可以通过以-W开头的选项,来逐个启用大部分gcc警告。例如,当使用switch语句的时候,如果没有对应的default标签,则-Wswitch-default选项会让gcc产生一个警告信息。-Wall可以启用大部分的警告。如果使用-Wall选项,但是希望取消其中的部分警告,可以在单独警告选项中-W后面插入no-来指出禁用这部分警告,例如,Wno-switch-default选项会关闭对于switch语句没有对应default标签的警告。

-Wextra选项会对合法但有疑问的表达式发出警告,也会对没有副作用和值被丢弃的表达式发出警告。

环境变量

变量 含义
CPATH、C_INCLUDE_PATH 用逗号分隔的目录列表,以提供头文件的搜索位置。在搜索完命令行-Idirectory选项所指定的目录后才搜索的目录。
COMPILER_PATH 用逗号隔开的目录列表,以提供gcc自身子程序的搜索位置。
GCC_EXEC_PREFIX 当GCC调用子程序时,需要加在子程序名称前面的前缀。
LIBRARY_PATH 用逗号隔开的目录列表,以提供链接器寻找链接库的位置。在搜索完命令行-Idirectory选项所指定的目录后才搜索的目录。
LD_LIBRARY_PATH 用逗号隔开的目录列表,以提供共享链接库文件的搜索位置。该变量不是由gcc读取,而是由执行文件读取,并动态链接到共享链接库。
TMPDIR 临时文件使用的目录(一般指定为/tmp)

参考文献:《C语言核心技术》--Peter Prinz, Tony Crawford

以上是关于使用gcc编译C程序的主要内容,如果未能解决你的问题,请参考以下文章

GCC:编译成程序集并明确与代码的对应关系?

为啥要为 c 和 c++ 使用 gcc 和 g++ 编译器驱动程序

gcc和g++的区别解析

vs怎么使用是gcc代码怎么写?

[opencv]使用g++编译opencv程序演示

gcc命令