Gcc和make的基本知识

Posted by persuez on July 14, 2018

注:以下实验环境为Ubuntu 18.0404LTS, gcc 7.3.0

GCC

  1. 编译单个源文件main.c
    • 生成目标文件:gcc -c main.c
    • 生成可执行文件: gcc -o main main.c,然后通过./main执行
  2. 链接多个目标文件并生成可执行文件:gcc -o test main.o test.o
  3. 链接库文件(以lib开头命名): 在默认路径查找-l(/lib和/usr/lib),链接libpam.a,gcc -o main main.o test.o -lpam;指定链接目录-L,gcc -o main main.o test.o -L/usr/local/lib/pam -lpam
  4. 在命令行中定义宏进行调试,gcc -D NDEBUG main.c,并且此时NDEBUG的值为1具体见gcc文档
  5. 优化:大部分用二级优化,gcc -c -O2 main.c

Makefile 初览

Makefile所做的工作就如在IDE上自动化build项目一样,但是Makefile需要我们自己编写,然后用make工具帮我们自动化执行。要做到自动化,需要以下几点:

  1. 目标(targets):你要告诉make要生成的目标文件是什么
  2. 规则(rules):你需要告诉make如何去生成目标文件
  3. 依赖(dependencies):因为之后可能会修改某个源文件,但可能这个被修改的文件会影响到其他文件,这个被修改的文件就是受影响文件的依赖。我们通过make文件定义了依赖之后,当依赖文件被修改之后,make只会重新编译那些受依赖影响的文件,而不会再浪费时间去重新编译不受影响的文件。注意:依赖是传递的,就是说A被B依赖,B被C依赖,那A被修改了,B就得重新生成,然后C肯定也得重新生成。

以下给出我们的例子(求整数的倒数):

/* main.c */
#include <stdio.h>
#include <stdlib.h>
#include "reciprocal.hpp"

int main(int argc, char **argv)
{
  int i;
  i = atoi(argv[1]);
  printf("The reciprocal of %d is %g\n", i, reciprocal(i));
  return 0;
}
// reciprocal.cpp
#include <cassert>
#include "reciprocal.hpp"

double reciprocal(int i)
{
  // i should be non-zero
  assert(i != 0);
  return 1.0 / i;
}
// reciprocal.hpp
#ifdef __cplusplus
extern "C" {
#endif

extern double reciprocal(int i);

#ifdef __cplusplus
}
#endif

我们要生成可执行文件reciprocal,因此需要目标文件reciprocal.omain.o。一般地,在Makefile中我们都会写cleantarget,以便清除所有make生成的文件(用make clean命令)。所以,最终的Makefile如下(以下的$(CFLAGS)先不解释:

reciprocal: main.o reciprocal.o
  g++ $(CFLAGS) -o reciprocal main.o reciprocal.o

main.o: main.c reciprocal.hpp
  gcc $(CFLAGS) -c main.c

reciprocal.o: reciprocal.cpp reciprocal.hpp
  g++ $(CFLAGS) -c reciprocal.cpp

clean:
  rm -f *.o reciprocal

从上面的格式中可以明显看出:targets写在左边,如main.o,然后跟着冒号,接着是一些依赖,然后规则写在下一行(先忽略$(CFLAGS))。需要注意的是:规则一行必须以一个Tab字符开头,不然make将会报错。运行如下: make运行结果 然后我们稍微修改以下main.c,运行结果如下: make运行结果 可以看到,重新make,只有重新编译了main.c和reciprocal(因为依赖于main.o)。 接着运行make cleanmake clean运行结果 现在我们说一下 $(CFLAGS),这是一个 make 系统变量,其中 CFLAGS 可以替换成其他变量名,如 a,b,c 等。CFLAGS指定头文件的搜索路径,如果头文件不在 gcc 的默认搜索路径下,则需要用此参数指定,如 CFLAGS=-I/usr/include -I/path/include,gcc 的默认搜索路径可以用 gcc -v -x c -E /dev/null 查看。

Makefile 编写编写规则

条件语句

例子

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
        $(CC) -o foo $(objects) $(libs_for_gcc)
else
        $(CC) -o foo $(objects) $(normal_libs)
endif

语法

conditional-directive
text-if-true
endif

或者

conditional-directive
text-if-true
else
text-if-false
endif

或者

conditional-directive-one
text-if-one-is-true
else conditional-directive-two
text-if-two-is-true
else
text-if-one-and-two-are-false
endif

conditional-directive 中,有以下五种的格式:

  1. ifeq (arg1, arg2)
  2. ifeq ‘arg1’ ‘arg2’
  3. ifeq “arg1” “arg2”
  4. ifeq “arg1” ‘arg2’
  5. ifeq ‘arg1’ “arg2”

此外,有一种特殊的比较,和空值比较:

ifeq ($(strip $(foo)),)
text-if-empty
endif

函数

  1. notdir:剥离文件的绝对路径,只保留文件名。
  2. $(dir <names...>):     名称:取目录函数 —— dir。     功能:从文件名序列 中取出目录部分。目录部分是指最后一个反斜杠("/")之前的部分。如果没有反斜杠,那么返回"./"。     返回:返回文件名序列 的目录部分。     示例:`$(dir src/foo.c hacks)`返回值是 "src/ ./"。