使用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!\n");
    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!\n");
 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!\n");
 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!\n");}
/*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

原文地址:https://www.cnblogs.com/addsomesugar/p/12184425.html

时间: 2024-10-06 02:20:21

使用gcc编译C程序的相关文章

gcc编译C++程序

gcc编译C++程序 单个源文件生成可执行程序下面是一个保存在文件 helloworld.cpp 中一个简单的 C++ 程序的代码: /* helloworld.cpp */#include <iostream>int main(int argc,char *argv[]){    std::cout << "hello, world" << std::endl;    return(0);}程序使用定义在头文件 iostream 中的 cout,向

bcc3.1和gcc编译同样程序的对比

1.这个使用bcc3.1精简版来编译c程序的,使用dosbox来模拟仿真的,bcc3.1可是1992年的工具了,相当老了.dos年代的工具,是16位编译工具, 2.下面使用msys2的最新版gcc-9.2.0来编译运行的.对比bcc3.1,平台已经更新到64位了 原文地址:https://www.cnblogs.com/CodeWorkerLiMing/p/12309790.html

在linux环境下尝试使用gcc编译一个程序,并验证-E\-S\-c选项

首先,进入Linux平台: 2.双击Terminal,进入: 3.编入命令 vim test.c ,进入新建文件test.c中,并且自己编写一个简单的c语言程序: 4.分别输入命令: ESC   :  w   q   ! 这5个命令回到编译页面,如若想修改则可输入命令 vim test.c  进入,输入命令i或者A命令 5.输入命令gcc -S test.c  预处理 6.输入命令 gcc -c test.c     预编译处理完成 7.输入命令    ./test 8.程序运行截图:

MAC中使用Vim和GCC编译C程序

1.打开终端 2.输入以下命令进入vim编辑器: vim a.c 3.进入编辑器后按i进入insert模式,然后键入以下代码: #include<stdio.h> int main(){ printf("\nhelloWorld!\n\n"); return 0; } 4.按ESC退出编辑模式,然后键入:wq,退出并保存刚编辑好的a.c 5.在终端中输入以下代码,把a.c编译为可执行文件 gcc a.c -o a 6.输入./a然后回车,就可以看到程序a.c的执行结果:在终

【LinuxC】GCC编译C程序,关闭随机基址

1.编译.链接和运行程序 C代码示例: #include <stdio.h> #include <stdlib.h> int main() { printf("hello world!\n"); exit(0); } 编译运行参数如下: [[email protected] Desktop]# gcc -o hello hello.c [[email protected] Desktop]# ./hello 2.关闭内存地址随机化机制(alsr) 关闭 [[em

Linux GCC编译警告:Clock skew detected. 错误解决办法

今天在虚拟机上用GCC编译一个程序的时候,出现了下面的错误: make: warning: Clock skew detected. Your build may be incomplete 试了make clean后再make,仍然是同样的问题,最后发现这个错误的原因在于系统时间比文件修改时间早,看了下我的系统时间竟然还是2012-01-13,而今天都已经是2012-01-31呢,于是修改时间后重新编译,问题解决. Linux下用date命令可查询和修改系统时间(root权限才可修改) 1 d

gcc编译C源文件

gcc编译C程序的主要过程是:预处理---编译---汇编---连接,其中:(以名为hello.c的源文件为例) 预处理:对各种预处理指令(#开头,如#include,#define)进行处理,以及删除注释和多余空白字符.生成被修改的源程序hello.i E是通知gcc对hello.c进行预编译:o是对命令输出结果进行导入操作 gcc -E hello.c -o hello.i 编译:对代码进行语法语义分析和错误判断,生成汇编代码文件hello.s S是通知gcc对目标文件hello.i进行编译

VIM+qmake编译示例程序HelloQt出错问题的解决(文件名一定要使用.cpp,否则就会默认使用gcc编译,当然通不过)

之前看到很多初学Qt的Linux友们在使用qmake编译第一个HelloQt或者HelloWorld程序时报错,并且始终找不到原因. 前几天我也遇到了同样的问题,我用的是<精通Qt4编程>书上的例子,将代码用Vim输入之后qmake,再make结果报错N行, 大部分是说什么什么为定义之类的,查了半天发现是只要把开头的几行#include<QtGui/..>里的文件包含进去就会出错, 但是不包含也不行,(后来发现网上不少人也在问这个问题,可是没说解决的),花了几个小时时间无果而终.

【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程

一. C程序编译过程 编译过程简单介绍 : C语言的源文件 编译成 可运行文件须要四个步骤, 预处理 (Preprocessing) 扩展宏, 编译 (compilation) 得到汇编语言, 汇编 (assembly) 得到机器码, 连接 (linking) 得到可运行文件; -- 查看每一个步骤的编译细节 : "-E" 相应 预处理, "-S" 相应 编译, "-c" 相应 汇编, "-O" 相应 连接; -- 每一个步骤