当前位置:Gxlcms > 数据库问题 > 学习的例子gcc+gdb+make

学习的例子gcc+gdb+make

时间:2021-07-01 10:21:17 帮助过:4人阅读

int main(void) { printf("Hello, world.\n"); printf("Hello, GCC.\n"); return 0; }

我们就将上面的程序存储到main.c的文件里,以下请跟着我敲命令吧,

gcc main.c
ls

在敲ls命令后你看到了什么?main.c文件夹下多了个a.exe的程序(Linux下可执行程序是a.out)。

好吧,既然你是exe格式,执行吧,

./a.out

看到什么,没错:Hello, GCC.

NOTES:
在Linux/Cygwin下运行程序使用"./可运行文件名称"。默认不设置时可运行文件名称为a.out或a.exe。

到此,你就该说。我已经会使用gcc了,然而,事实上你还差远了:

  1. 你知道怎么生成汇编文件吗?
  2. 你知道怎么编译c++文件吗?
  3. 你知道怎么查看预处理之后的结果吗?
  4. 你看VC++。人家都会编译生成*.obj的文件,你知道怎么使用gcc编译得到吗?
  5. 你知道怎么编译多个文件吗?
  6. 你知道如何才干调试吗?
  7. ……

假设你有哪项不知道,不着急,请Go on!

在这之前,你必须了解C代码生成可运行文件的过程。共4步:预处理、编译、汇编、链接。

技术分享

gcc生成可运行文件的过程

还是Hello, GCC的样例,请跟着敲命令。

gcc main.c -o main

-o 选项表示生成目标文件名称为main.exe。

gcc -E main.c -o main.i

-E 选项表示预处理操作,预处理就是将宏定义展开,头文件展开。预处理之后的目标文件保存在main.i,这时,你能够查看main.i的预处理结果。

cat _main.c
gcc -S main.c -o main.s

-S 选项表示编译操作,其结果将生成汇编文件(*.s文件,这里使用-o选项定义目标文件为main.s)。

我们也能够查看分析上述Hello代码的汇编代码,

    .file   "main.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC0:
    .ascii "Hello, world.\12\0"
LC1:
    .ascii "Hello, GCC.\12\0"
    .text
.globl _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    andl    $-16, %esp
    movl    $0, %eax
    addl    $15, %eax
    addl    $15, %eax
    shrl    $4, %eax
    sall    $4, %eax
    movl    %eax, -4(%ebp)
    movl    -4(%ebp), %eax
    call    __alloca
    call    ___main
    movl    $LC0, (%esp)
    call    _printf
    movl    $LC1, (%esp)
    call    _printf
    movl    $0, %eax
    leave
    ret
    .def    _printf;    .scl    3;  .type   32; .endef

在DSP、ARM等嵌入式平台上。使用gcc编译得到汇编,再依据汇编代码进行优化是一种经常使用的方法。

NOTES:
生成汇编文件(包括汇编代码的文件)的过程是编译。不是汇编,汇编是将汇编代码转换成目标文件*.obj的过程。 从这点上理解,汇编文件生成可运行文件的过程是没有编译操作的。

gcc -c main.c -o main.obj  

-c 选项将源文件生成目标文件main.obj,main.obj事实上已经是一种近似可运行文件了,通过链接操作链接对应的库就能够运行了。

第4步的链接直接使用gcc main.c -o main就能够完毕。

gcc是工具的集合

从以下的命令你将更加直观的看到:gcc事实上是一套从预处理、编译、汇编到链接工具的集合。

rm main.i main.s main.obj 
cpp main.c > main.i
gcc -S main.i
as main.s -o main.o

上面分别使用了cpp预处理、gcc编译、as汇编。链接能够使用ld命令,只是操作复杂些。

因此。gcc仅仅是帮我们将多个复杂的操作放在一个命令中,是我们的软件开发过程变得更加高效自己主动化。

gcc的一些其他选项

  • -C 仅仅能配合-E使用,告诉预处理器不要丢弃凝视.
gcc -E -C main.c -o main.i  
  • -M 告诉预处理器输出一个适合make的规则,用于描写叙述各目标文件的依赖关系.
gcc -M main.c -o main_makerule
cat main_makerule
main.o: main.c /usr/include/stdio.h /usr/include/_ansi.h   /usr/include/newlib.h /usr/include/sys/config.h   /usr/include/machine/ieeefp.h /usr/include/sys/features.h   /usr/include/cygwin/config.h   /usr/lib/gcc/i686-pc-cygwin/3.4.4/include/stddef.h   /usr/lib/gcc/i686-pc-cygwin/3.4.4/include/stdarg.h   /usr/include/sys/reent.h /usr/include/_ansi.h /usr/include/sys/_types.h   /usr/include/machine/_types.h /usr/include/machine/_default_types.h   /usr/include/sys/lock.h /usr/include/sys/types.h   /usr/include/machine/types.h /usr/include/cygwin/types.h   /usr/include/sys/sysmacros.h /usr/include/stdint.h   /usr/include/endian.h /usr/include/bits/endian.h   /usr/include/byteswap.h /usr/include/sys/stdio.h   /usr/include/sys/cdefs.h
  • -w 禁止各种类型的警告,不推荐使用
  • -Wall 显示各种类型的警告,推荐使用,这能够让自己的程序变得更加规范化
  • -v 显示编译过程的具体信息,Verbos简称
  • -llibrary 连接名为library的库文件.
  • -Idir 在头文件的搜索路径列表中加入dir文件夹
  • -Ldir 在`-llibrary‘选项的搜索路径列表中加入dir文件夹.

NOTES:
在大project编译链接过程中,非常多类似于“Undefined ...”的错误都是由-llibrary、-Idir或-Ldir的设置错误造成的。

  • -Dname 宏定义某个name宏,这个宏是全局的,在控制程序上非常有帮助 比方有例如以下main.c源程序,通过_DEBUG宏能够控制是否打印结果,
    #include <stdio.h>
    int main(void)
    {
        int a = 2;
        int b = 3;
        int c = a + b;

    #ifdef _DEBUG
        printf("c=%d\n", c);
    #endif
        return 0;
    }

对照以下使用和不使用-D_DEBUG的执行结果

    gcc -D_DEBUG main.c -o main 
    ./main 
    gcc main.c -o main 
    ./mian
  • -O/O1 代码优化。对于大函数,优化编译占用略微多的时间和相当大的内存
  • -O2 多优化一些.除了涉及空间和速度交换的优化选项,执行差点儿全部的优化工作.比如不进行循环展开(loopunrolling)和函数内嵌(inlining).和-O选项比較,这个选项既添加了编译时间,也提高了生成代码的 执行效果.
  • -O3 优化的很多其它.除了打开-O2所做的一切,它还打开了-finline-functions选项.

  • -g 以操作系统的本地格式(stabs,COFF,XCOFF,或DWARF)产生调试信息. 仅仅有使用了-g才干使用gdb工具进行调试

gcc -g main.c -o main 
gdb main 

有关gdb的操作有非常多。将专门详述。

多文件编译

好吧,我们写个多文件小程序:Example2。

main.c

#include <stdio.h>
#include "add.h"

int main(void)
{
    int a = 2;
    int b = 3;
    int c = add(a,b);

#ifdef _DEBUG
    printf("c=%d\n", c);
#endif
    return 0;
}

add.c

int add(int a, int b)
{
    return (a+b);
}

add.h

#ifndef _ADD_H
#define _ADD_H

extern int add(int a, int b);

#endif

使用gcc编译多文件的方法是将多个源文件加入到gcc编译选项中,

gcc -D_DEBUG main.c add.c -o main 
./main 

显演示样例如以下运行结果。

技术分享

NOTES:
上面未将add.h加入到编译文件里是由于:C语言的编译是以.c文件为单位的,每一个.c文件都会编译相应到一个.s和.obj文件,而.h文件不会。在上面的样例中,我们仅仅要保 证在main.c在编译的时候可以找到add函数,这是通过#include "add.h"实现的。在链接的时候,main.obj会自己主动找到到add.obj中的add符号,这都是链接器的功劳。

共享库与静态库

库时编译好的目标文件的一个打包。在链接时被载入到程序中。分为共享库和静态库。如之前使用到的printf定义就在库libc中,我们仅仅要包括stdio.h就能使用了(事实上还要在gcc中使用-llibray选项,仅仅只是gcc默认包括了该选项)。

  • 静态库:在Linux下是.a文件,在Windows下是.lib文件。

    在链接时将用户程序中使用到外部函数机器码复制到程序可运行区

  • 共享库:在Linux下是.so文件,在Windows下是.dll文件。

    在链接时,仅仅在程序可运行区建立一个索引表,而不拷贝机器代码。在程序运行时。才依据索引表载入外部函数的机器码。共享库相对于静态库的长处是降低了可运行程序代码量的大小。同一时候共享库可同一时候被多个运行程序调用,也能降低内存空间。

    ![][lib]

不管是静态库还是共享库,在gcc选项中都要使用-l和-L分别制定库名和库路径。

仍以上个add程序为例,说说静态库和共享库的创建和使用,先加入sub函数, sub.c

int sub(int a, int b)
{
    return (a-b);
}

sub.h

#ifndef _SUB_H
#define _SUB_H

extern int sub(int a, int b);

#endif

main.c

#include <stdio.h>
#include "add.h"
#include "sub.h"

int main(void)
{
    int a = 2;
    int b = 3;
    int c = add(a,b);
    int d = sub(a,b);

#ifdef _DEBUG
    printf("c=%d\n", c);
    printf("d=%d\n", d);
#endif
    return 0;
}
  • 静态库的生成须要使用到gcc和ar工具
gcc -c add.c sub.c
ar -r libmymath.a add.o sub.o

技术分享

这样,我们仅仅要将.a文件和.h文件打包,add和sub就能够在随意地方使用了。

NOTES:
请注意上图给出的在编译main.c时怎样链接到静态库的?(-lmymath -static选项)

之前说过,静态库链接的代码量将比共享库链接要大,我们且先看看静态链接后的代码量。稍后做比較 技术分享

  • 共享库的生成须要使用到gcc工具
gcc -shared -fPIC add.c sub.c -o libmymath.so 
ln -s libmymath.so libmymath.dll   # Windows下才须要
gcc main.c -lmymath -L./ -o main 

-shared 表示共享库。-fPIC 表示生成与位置无关的代码。

技术分享

NOTES:
在Linux下,共享库链接的的可运行程序运行时还是会出现找不到库问题,这时有两种方法:

  1. 能够把当前路径增加/etc/ld.so.conf中然后执行ldconfig,或者以当前路径为參数执行ldconfig(要有root权限)
  2. 把当前路径增加环境变量LD_LIBRARY_PATH 中

相同,来看看使用共享库生成的可运行文件大小。

技术分享

诶,0x9d4,人家静态库总大小才0x990字节。你怎么说静态库要比共享库大呢?

第一。静态库生成代码量比共享库大指的是——代码量,代码存储在text段。明显嘛,共享库代码段大小1712要比静态库的1728小。

第二, 前面说过。共享库在链接成可运行文件的时候不是直接拷贝目标文件机器码,而是生成符号表。对,符号表。从上面的结果来看。符号表应该存储在data段,所以共享库的data段比静态库要大。由于我们这里的add.c和sub.c的代码量生成的机器码都很小,使用共享库生成符号表的方法反而使可运行文件占用的磁盘空间更大了。仅仅是普通情况下。使用共享库的可运行文件占用的磁盘空间将比静态库的小.

库路径、文件路径与环境变量

之前已经说过,通过-I能够指定头文件路径。-L指定库路径,-l指定库名。从上一节也看到了它们的使用。

那么系统默认的头文件路径和库路径是在哪呢?

cpp -v

能够查看默认的头文件路径.

gcc -v main.c -lmymath -L. -o main

加入-v选项,就能够查看默认的头文件路径.

gcc提供几个默认的环境变量:

  1. PATH:可运行文件和动态库(.so, .dll)的搜索路径
  2. CPATH:头文件搜索路径
  3. LIBRARY_PATH:库搜索路径,包含动态库和静态库

几个实用查看目标文件的工具

  • file

    显示目标文件格式和执行环境的体系结构(ARM还是x86)

  • nm

    列出目标文件的符号表

  • ldd

    列出可执行文件在执行时所须要的共享库

  • size

    列出目标文件里节的名称和大小。

    上面已经使用过该工具。

  • readelf

    显示elf目标文件的完整结构,包含elf头中的编码信息,包含size和nm功能。最经常使用的方法是參数是-d和-h參数。

  • objdump

    全部二进制工具之母。最大作用是反汇编.text节中的二进制信息。

    最经常使用的格式是objdump -D -S filename,显示反汇编信息。若要反汇编与源代码同一时候显示。则在gcc编译时要使用-g选项。

NOTES: 上面全部的工具都能够通过 [toolname] --help获得相关的參数帮助信息。

3 GDB的使用

gdb是一个调试工具。与gcc一样,gdb可调试包含C、C++、Java、Fortran、汇编等多种语言。

gdb的原始开发人员是Richard M.Stallman也是开源运动中的一位领袖级级人物。

内存布局及栈结构

假设你还不知道malloc分配变量与局部变量的差别的话,那你还不够格称为程序猿。由于程序中的定义或不论什么的内存分配都直接和可运行文件在用户进程虚存空间的布局映射有关,

技术分享

如上图所看到的。包含:

  • 内核虚拟空间(高地址):用户进程是无法訪问到的
  • 用户栈:局部变量存储的地方,该部分须要用户手动初始化
  • 执行时共享库空间:C库函数一般都会映射到这里
  • 用户堆:malloc分配的空间
  • 初始化读写数据区:该部分系统会在编译时初始化,包含全局变量、static声明变量等
  • 仅仅读代码段:存放程序代码的地址,程序执行时都要将程序可执行代码装载到内存后才干执行

gdb调试C程序

给定一段程序main.c:

#include <stdio.h>
int add(int a, int b)
{
    int c = a + b;
    return c;
}

int main(void)
{
    int i = 0;
    int j = 3;
    int k = add(i,j);
    printf("i=%d, j=%d, k=%d\n", i,j,k);
    return 0;
}

使用gcc编译及gdb调试程序的方法例如以下:

Administrator@DADI-20131210YK /cygdrive/e/MyDesigner/Projects/notes/codes/实例学习gcc+gdb+make/gdb
$ gcc -g main.c -o main          ### 注:编译时使用-g选项才干生成符号表用于gdb调试

Administrator@DADI-20131210YK /cygdrive/e/MyDesigner/Projects/notes/codes/实例学习gcc+gdb+make/gdb
$ gdb main
GNU gdb (GDB) 7.3.50.20110821-cvs (cygwin-special)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-cygwin".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /cygdrive/e/MyDesigner/Projects/notes/codes/实例学习gcc+gdb+make/gdb/main...don
e.
(gdb) l main                     ### 注:list查看程序,l [函数名/行数]
16
17          return c;
18      }
19
20      int main(void)
21      {
22          int i = 0;
23          int j = 3;
24
25          int k = add(i,j);
(gdb)                            ### 注:Enter按键接上面继续查看程序
26
27          printf("i=%d, j=%d, k=%d\n", i,j,k);
28
29          return 0;
30      }
31
(gdb) b 23                       ### 注:在23行加入断点
Breakpoint 1 at 0x4010d5: file main.c, line 23.
(gdb) b add                      ### 注:在add函数入口加入断点
Breakpoint 2 at 0x401096: file main.c, line 15.
(gdb) b 29                       ### 注:在29行加入断点
Breakpoint 3 at 0x401112: file main.c, line 29.
(gdb) info break                 ### 注:查看已加入的断点信息
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x004010d5 in main at main.c:23
2       breakpoint     keep y   0x00401096 in add at main.c:15
3       breakpoint     keep y   0x00401112 in main at main.c:29
(gdb) r                          ### 注:运行程序
Starting program: /cygdrive/e/MyDesigner/Projects/notes/codes/实例学习gcc+gdb+make/gdb/main
[New Thread 2492.0x36c]
[New Thread 2492.0x1140]

Breakpoint 1, main () at main.c:23
23          int j = 3;
(gdb) p i                        ### 注:print打印变量i的值
$1 = 0
(gdb) n                          ### 注:next下一步(把函数当一条语句直接跳过)
25          int k = add(i,j);
(gdb) s                          ### 注:step下一步(会运行到函数内部)

Breakpoint 2, add (a=0, b=3) at main.c:15
15          int c = a + b;
(gdb) c                          ### 注:continue从运行到的当前位置继续往下运行,直到遇到下一个断点
Continuing.
i=0, j=3, k=3

Breakpoint 3, main () at main.c:29
29          return 0;
(gdb) finish                     ### 注:直接运行到当前函数的结尾处,对main函数不起作用
"finish" not meaningful in the outermost frame.
(gdb) c                          ### 注:continue继续运行
Continuing.
[Inferior 1 (process 2492) exited normally]
(gdb) q                          ### 注:程序运行结束。quit退出gdb

Administrator@DADI-20131210YK /cygdrive/e/MyDesigner/Projects/notes/codes/实例学习gcc+gdb+make/gdb
$

好了,就这么简单,仅仅要会了以上几个命令,你就能够开开心心的使用gdb了。

gdb还有非常多高级的内容。本文目标为入门。很多其它内请不吝你的手指依次敲击:

>> gdb
>> help  

參见參考文献[6]。这是gdb的一份官方手冊。

4 使用Makefile构建project

Make是构建project的工具。Make工具对用户编写的Makefile进行解析,实现仅仅须要一个命令就能够编译、链接整个project。大部分熟悉VC++的人都漠视了Make工具的存在(VC++的Make工具叫nmake)。这就是为什么使用VC++多文件编译链接能一步搞定的原因所在。

我们这里当然不是去讨论VC++的nmake,而要讨论的是GNU Make的Makefile。Makefile文件用于描写叙述整个project的编译、链接的规则。

编写Makefile

仍以Example2为例,源码文件夹下新建Makefile文件(对了,文件名称就是“Makefile”。没.txt等不论什么后缀),

vim Makefile

Makefile文件内容为

main:main.o add.o sub.o                 # 目标:依赖
    gcc main.o add.o sub.o -o main      # 命令(必需以TAB开头)

main.o:main.c
    gcc -c -D_DEBUG main.c -o main.o
add.o:add.c
    gcc -c -D_DEBUG add.c -o add.o
sub.o:sub.c
    gcc -c -D_DEBUG sub.c -o sub.o


.PHONY:clean
clean:
    -rm main *.o

好了。回到命令行。使用make命令看看Makefile的效果:

make 
./main 

什么,输出了正确的结果。那就对了。以下我们来分析下上面的Makefile。

  • Makefile的格式
目标:依赖
    命令(以TAB开头)

目标是链接后的可运行文件名称;依赖是project中的用于编译的c文件和用于连接的*.o文件的集合;就用gcc编译的project而言。命令就是gcc命令,能够使用随意的gcc參数。

  • 使用make命令解析Makefile文件,解析的文件名称能够是Makefile或makefile,假设是其他名称。则须要使用make -f [filename]指定文件名称。

    强烈建议使用Makefile作为文件名称(符合Linux的哲学——简洁、首字母大写easy突出文件位置)。

  • Makefile能够有多个目标(main.o add.o sub.o)。但仅仅能有一个终于目标(main)。

    Makefile文件里第一条规则中的目标将确定为终于目标。

    make命令默认运行终于目标。若仅仅运行Makefile中其他目标,使用make [Target]。如要清除project下的目标文件,使用

make clean
  • Makefile中将那些没有不论什么依赖仅仅有运行动作的目标称伪目标(clean),使用.PHONY声明。

    伪目标不能作为终于目标。

  • Makefile中使用变量:Makefile中的变量将是按字符串的方式进行替换。以下是一些系统特殊的变量。

$^: 代表全部依赖文件
$@:代表目标
$<:代表依赖文件里的第一个依赖文件

变量能够大大简化Makefile的编写复杂度。使用变量后的Makefile例如以下:

CC=gcc
OBJS=main.o add.o sub.o
CFLAGS=-D_DEBUG

main:$(OBJS)
    $(CC) $(CFLAGS) $^ -o $@

main.o:main.c
    $(CC) $(CFLAGS) -c $^ -o $@
add.o:add.c
    $(CC) $(CFLAGS) -c $^ -o $@
sub.o:sub.c
    $(CC) $(CFLAGS) -c $^ -o $@

.PHONY:clean
clean:
    -rm main *.o

当中CC、OBJS、CFLAGS都是自己定义的Makefile变量,$^和$@是系统特殊的变量。

人气教程排行