C/C++ - gcc

GCC #

gcc(GNU Compiler Collection,GUN编译器套件),是由GNU开发的变成语言编辑器

gcc原本作为GNU操作系统大官方编译器,现已被大多数类Unix操作系统(如Linux、BSD、Mac OS X 等)采纳为标准的编译器,gcc同样适用于微软的Windows。

gcc最初用于编译C语言,随着项目的发展gcc已经成为了能够编译C、C++、Java、Ada、fortran、Object C、Object C++、Go语言的编译器大家族。

编译命令格式:

gcc [-optionl] ...

g++ [-optionl] ...

-o file 指定生成的输出文件名为file
-E 只进行预处理,仅预编译不做其它处理,生成预编译文件*.i
-S 只进行预处理和编译,只编译不汇编,生成汇编代码文件*.s
-c 只进行预处理、编译和汇编,只编译不链接,生成目标文件*.o

-g 在可执行程序中包含标准调试信息
-v 打印出编译器内部编译各过程的命令行信息和编译器的版本

-I dir (字母i的大写 include目录地址)指定编译中引入的头文件地址,多个用;分割
-L dir (字母l的大写 Librar库地址)编译中引入的库地址,多个用;分割
-l library (字母L的小写)链接名为library的库文件
-shared 运行动态编译,尽可能的链接动态库,或者生成代码生成动态库时使用
-FPIC 或(-fpic) 生成使用相对地址无关的目标代码

-static 链接静态库

生成libxx动态库,并-L(库地址) -l xx 库名生成可执行文件

gcc -fPIC -shared -o libxx.dll f1.c f2.c
gcc -o hello hello.c -L . -l xx

如果命令中不包括输出可执行文件的文件名,可执行文件等的文件名会自动生成一个默认名,Linux平台为a.out,Windows平台为a.exe

-I 指定头文件路径
-l 指定库名
-L 指定库存储路径

使用 -I(大i) 指定 头文件路径。使用 -L 指定 库存储路径。 使用 -l(小L) 指定 库名(去除lib前缀,.a 后缀)。

gcc test.c -o test -I ./inc -l mymath -L ./lib

C编译 #

编译步骤 #

C代码编译成可执行程序经过4步:

  • 预处理:宏定义展开、头文件展开、条件编译等,同时将代码中的注释删除,这里并不会检查语法
  • 编译:检查语法,将预处理后文件编译生成汇编文件
  • 汇编:将汇编文件生成目标文件(二进制文件)
  • 链接:C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去

编译过程

分步编译

预处理: gcc -E hello.c -o hello.i
编译  : gcc -S hello.i -o hello.s
汇编  : gcc -c hello.s -o hello.o
链接  : gcc    hello.o -o hello.exe

ldd 命令能查看链接过程依赖的库(动态链接库,不同操作系统不一样即为动态)

编译步骤实践 #

hello.c文件

#include <stdio.h>
int main()
{
    puts("Hello wolrd");
    return 0;
}

执行编译

PS D:\study\vs\example> gcc -S hello.i -o hello.s
PS D:\study\vs\example> gcc -c hello.s -o hello.o
PS D:\study\vs\example> gcc    hello.o -o hello.exe
PS D:\study\vs\example> ldd .\hello.exe
        ntdll.dll => /c/windows/SYSTEM32/ntdll.dll (0x7ffb04de0000)
        KERNEL32.DLL => /c/windows/System32/KERNEL32.DLL (0x7ffb04710000)
        KERNELBASE.dll => /c/windows/System32/KERNELBASE.dll (0x7ffb01fa0000)
        msvcrt.dll => /c/windows/System32/msvcrt.dll (0x7ffb040a0000)

双击hello.exe执行程序,会在控制台输出Hello world

执行过程

预处理 #

C语言对源程序处理的四个步骤:预处理、编译、汇编、链接

预处理是在程序源代码被编译之前,由预处理器(Preprocessor)对程序源代码进行的处理,这个过程并不对程序的源代码进行解析,但它会把源代码分割或处理成特定的符号为下一步的编译做准备工作

example.c

#include <stdio.h> // 头文件直接展开

#define PRICE 10 // 宏定义直接替换

#if 0   // 条件编译,如果不满足,代码丢掉
void f1() 
{
    printf("helloworld\n");
}
#endif

int main(int argc, char *argv[])
{
    printf("hello world");
    PRICE * 12;
    return 0;
}
 gcc -o example.i -E example.c

example.i文件内容

0 ".\\example.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 ".\\example.c"
# 1 "C:/msys64/mingw64/include/stdio.h" 1 3
# 9 "C:/msys64/mingw64/include/stdio.h" 3
 

1、文件包含指令(#include#

“文本包含处理” 是指一个源文件可以将另外一个文件的全部内容包含进来

C语言提供了#include命令来实现“文件包含”的操作

头文件包含

#include <>#include ""的区别

  • ""表示系统先在源文件所在的当前目录找包含,如果找不到,再按系统指定的目录检索。
  • <>表示系统直接按系统指定的目录检索。

注意:

  1. #include <>常用于包含库函数的头文件
  2. #include "" 常用于包含自定义的头文件
  3. 理论上#include可以包含任意格式的文件(.c.h等),但一般用于头文件的包含

2、宏定义 #

宏定义就是编译器做的“批处理”,与C语言本身没半毛钱关系。

在编译预处理时,将程序中#define的所有变量替换,这种方法使用户能以一个简单的名字代替一个长的字符串,在预编译时将宏名替换成字符串的过程称为“宏展开”。

宏定义,只在宏定义的文件中起作用

说明:

  1. 宏名一般用大写,以便于与变量区别
  2. 宏定义可以是常数、表达式等
  3. 宏定义不作语法检查,只有在编译被宏展开后的源程序才会报错
  4. 宏定义不是C语言,不在行末加分号
  5. 宏名有效范围为从定义到本源文件结束
  6. 可以用#undef命令终止宏定义的作用域
  7. 在宏定义中,可以引用已定义的宏名

条件宏定义和使用

#include <stdio.h>

void main(int argc, char *argv[])
{
#ifdef DEBUG
    printf("test\n");
#endif
    printf("hello world\n");
}

gcc -g -o hello1.exe  hello.c -D DEBUG
./hello1.exe
> test
> hello world
gcc -g -o hello.exe  hello.c
./hello.exe   //未在终端输出test
> hello world
带参数的宏定义(宏函数) #

在项目中,经常把一些短小而又频繁使用的函数写成宏函数,这是由于宏函数没有普通函数参数压栈、转跳、返回等开销,可以调高程序的效率。 宏通过使用参数,可以创建外形和作用都与函数类似地类函数宏(function-like macro)

宏的参数也用圆括号括起来

#define SUM(x,y) ((x)+(y))
void test()
{
	// 仅仅是做文本替换
	int ret = SUM(10,20);
	printf("ret:%d\n", ret);
}

注意:

  1. 宏的名字中不能有空格,但是在替换的字符中可以有空格。ANSI C允许在参数列表中使用空格
  2. 用括号括住每一个参数,并括住宏的整体定义
  3. 用大写字母表示宏的函数名。
  4. 如果打算宏代替函数来加快程序运行速度。假如在程序中只使用一次宏对程序的运行时间没有太大提高。
一些特殊的预定宏 #

C编译器提供了几种特殊形式的预定义宏,在实际编程中可以直接使用,很方便

// __FILE__ 宏所在文件的源文件名
// __LINE__ 宏所在的行的行号
// __DATE__ 代码编译的日期
// __TIME__ 代码编译的时间

void test()
{
	printf("$s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
}

3、条件编译 #

一般情况下,源程序中所有的行都参加编译。

但有的时候希望对比分源程序行只在满足一定条件时才编译,即对这部分源程序行指定编译条件

条件编译

防止头文件被重复包含引用

#ifndef _SOMEFILE_H
#define _SOMEFILE_H

// 需要声明的变量、函数
// 宏定义
// 结构体

#endif

编译(转换为汇编代码) #

gcc -o example.s -S example.i

编译后,即为汇编指令

	.file	"example.c"
	.text
	.def	printf;	.scl	3;	.type	32;	.endef
	.seh_proc	printf
printf:
	pushq	%rbp
	.seh_pushreg	%rbp
	pushq	%rbx
	.seh_pushreg	%rbx
	subq	$56, %rsp
	.seh_stackalloc	56
	leaq	48(%rsp), %rbp
	.seh_setframe	%rbp, 48

汇编 #

把汇编代码进行汇编,形成二进制文件

gcc -o example.o -c example.s

链接 #

上面经过汇编生成的二进制文件还不能执行,因为有些库函数不知道在哪

  • 动态链接
gcc -o example.exe example.o
ldd example.exe //查看动态链接库
  • 静态链接
gcc -o hello.a hello.o -static

链接后生成的文件包含调用的链接函数,一个显而易见的区别是,静态链接的文件大小比动态链接的文件要大得多(因为生成的文件中,包含了引用的全部函数实现

库的封装和使用 #

库是已经写好的、成熟的、可复用的代码。每个程序都需要依赖很多底层库,不可能每个人的代码从零开始编写,因此库的存在具有非常重要的意义。

我们的开发的应用中经常有一些公共代码是需要反复使用的,就把这些代码编译为库文件。

库可以简单看成一组目标文件的集合,将这些目标文件经过压缩、打包后形成的一个文件。像在Windows这样的平台上,最常用的C语言库是由集成开发环境所附带的运行库,这些库一般由编译厂商提供。

静态库(创建、使用) #

f1.c

#include <stdio.h>

void f1() {
    printf("this is f1 function\n");
}

f2.c

#include <stdio.h>

void f2() {
    printf("this is f2 function\n");
}

hello.c

#include <stdio.h>

extern void f1();
extern void f2();


void  main()
{
    f1();
    f2();
}

把f1.c、f2.c 编译成二进制文件*.o

gcc -c f1.c f2.c 

生成静态库

ar -crv libx.a f1.o f2.o

ar 命令用于建立或修改备存文件,或是从备存文件中抽取文件

ar[-dmpqrtx][cfosSuvV][a《成员文件》][b《成员文件》][i《成员文件》][备存文件][成员文件]

c 创建备存文件 v 程序执行时显示详细的信息 r 将文本插入备存文件中 libx.a - 以lib开头,.a结尾,中间x为库的名字

通过静态库生成可执行文件hello.exe

gcc -o hello hello.c -static -L . -l x 

动态库(创建、使用) #

生成动态库

gcc -fPIC -shared -o libxx.dll f1.c f2.c

GCC 生成动态链接库 .so 文件 (-shared 和 -fPIC 选项)

  • fPIC f后面跟一些编译选项,PIC是其中一种,表示生成位置无关代码(Position Independent Code)
  • shared 表示调用动态库

链接生成目标文件

gcc -o hello hello.c -L . -l xx
ldd hello.exe

查看输出,就能查看到libxx.dll的链接

ntdll.dll => /c/windows/SYSTEM32/ntdll.dll (0x7ffb04de0000)
KERNEL32.DLL => /c/windows/System32/KERNEL32.DLL (0x7ffb04710000)
KERNELBASE.dll => /c/windows/System32/KERNELBASE.dll (0x7ffb01fa0000)
msvcrt.dll => /c/windows/System32/msvcrt.dll (0x7ffb040a0000)
libxx.dll => /d/study/vs/example/staticlibrary/libxx.dll (0x7ffaea630000)