编译与调试(C++)

[TOC]

编译

g++ helloworld.cpp 命令被分为四个步骤,预处理、编译、汇编、链接

编译与链接

1545885468880

预处理

首先是源码相关和头文件,如iostream被预处理其处理成一个.i 文件,第一步相当于该命令

1
g++ -E helloworld.cpp -o hellword.i

其中-E表示只进行预编译,直接输出预编译结果

预处理命令主要处理以#开头的预编译命令,如#include #define

  1. 将所有的#define删除, 展开所有的宏定义
  2. 处理所有的条件编译指令 #if、#elif、#else、endif
  3. 处理#include预编译指令,将文件插入指定位置
  4. 过滤注释内容
  5. 添加行号标识,一遍编译器调试时产生行号信息
  6. 保留所有的#progama指令

编译

编译过程就是将文件进行一列的词法分析、语法分析、语义分析及优化产生汇编代码文件,这往往是程序最复杂的部分

1
g++ -S helloworld.i -o helloworld.s

编译器将高级语言翻译成机器语言的一个工具,编译过程大致有以下几部分

1545916610543

链接

每个源代码模块独立的编译,然后按要求组装起来,这个过程就是链接。链接的主要部分就是把各个模块之间的部分处理好,使得各个模块可以正确的衔接。

最基本的链接过程如图所示,每个模块的源代码文件经过编译器编译成目标文件,目标文件和库一起链接成可执行文件。

1546524867263

库其实就是一组文件的包,一些代码编译成目标文件后打包存放。

每个目标文件除了自己的数据和二进制代码外,还提供三个表,未解决符号表、导出符号表、地址重定向表

  • 未解决符号表 编译单元里引用但是定义不在本编译单元定义
  • 导出符号表 对本单元定义,愿意给其他单元使用的符号和地址
  • 地址重定向表 本编译单元对自身地址的引用

extern 声明置入未解决符号表,不置入导出符号表

static 属于内部链接

静态链接

链接分为静态链接和动态链接。对函数库的链接放在编译使其完成的 是静态链接。所有相关的目标文件与涉及到的函数被链接合成一个可执行文件。程序运行时与函数库毫无关系。因为所有需要的函数已经被复制到相应位置,这些函数被称为静态库,通常命名为libxxx.a

动态链接

除了静态链接、也可以把一些库函数链接载入推迟到运行使其,这就是动态链接库。动态库文件名规范和静态库类似,动态库的扩展名为so,

静态链接和动态链接的区别
  1. 动态链接库利于进程资源共享

当某个程序在运行中调用某个动态链接库函数时,操作系统会在内存中寻找库函数的拷贝。这样虽然带来一些动态链接的开销,却大大节省了内存资源。C语言标准库就是动态链接库,所有程序共享一个C语言标准库的代码段,而静态链接库则不同,系统中每个静态链接库都需要拷贝到自己的代码段,这样更耗费内存。

  1. 程序的升级更简单

如果库发生变化,使用库需要重新编译。使用动态库,只要动态库的接口没变,只需要更换动态库

  1. 链接载入程序员控制
  2. 由于静态库是编译时,库函数装到程序中,静态库会快一些

调试

strace

系统调用 为了创建文件、进程、复制文件、应用程序必须和操作系统交互。但是应用程序不能直接访问linux内核。它不能访问内核的内存、也不能调用内核函数。这样就需要操作系统上的内建函数,被称为系统调用

strace通过跟踪系统调用让开发知道程序在后台做的事

1546524881392

strace查看系统调用

gdb

gdbgcc 的调试工具 1自定义随心所欲的运行程序 2 可以让被调试的程序在断点停住 3 程序停住时可以检查程序的运行状态 4 动态改变程序环境

TOP

1545999336751

1545999426692

PS

Linux中 ps列出当前的进程快照, kill 用于杀死进程

linux上进程有5种状态

  1. 运行 R(正在运行或队列中等待)
  2. 中断 S(休眠、受阻、在等待某个条件的形成或接受信号)
  3. 不可中断 D(收到信号不唤醒、不可运行)
  4. 僵死 Z(进程已终止,但是进程描述符仍存在,知道父进程调用wait4()释放)
  5. 停止 T(进程收到信号后停止运行)

valgrind

valgrind 是一套linux下开放源代码的仿真调试工具,它模拟了一个CPU环境,并给其他服务提供插件

  1. Memcheck 这是运用最广泛的工具,一个重量级的内存检查器,发现大多数的内存错误使用情况。
  2. callgrind 收集程序的运行数据,建立函数调用关系图
  3. cachegrind 检查程序中缓存出现的问题,模拟CPU的一级、二级缓存
  4. Helgind 检查多线程的竞争问题 寻找多个线程访问而没有一贯加锁的区域。
  5. Massif堆栈分析器,测量堆、栈的大小
  6. Extension 。。。

Linux 程序内存空间布局

下图是一个典型的内存空间布局

1546000621186

  1. 代码段 通常指存放程序执行代码的一块内存区域。这部分大小在程序运行前已经确定,并且内存区域只读,某些架构也允许可写
  2. 初始化数据段,存放程序中已初始化的全局变量
  3. 未初始化数据段, 未初始化全局变量的一块区域
  4. 堆 堆用于储存程序运行时动态分配的内存段,它的大小不确定,可以动态的扩张或缩减,程序调用malloc及free来动态分配内存,当进程调用malloc、free时,新配的内存被动态添加到堆上或删去
  5. 栈 存放程序的局部变量,并且用户函数调用的传参和返回

堆栈的区别

1 申请方式不同

栈: 系统自动分配,声明在函数中一个局部变量;系统自动在栈中为其分配空间

堆: 需要程序员自己申请。并指明大小

2 申请后系统的相应不同

栈: 只要栈的剩余空间大于申请空间,系统便提供内存,否则报异常

堆: 操作系统有一个记录空闲内存的链表,系统受到申请会遍历链表去找一块内存,从空间链表中删去,并将剩下的空间再放回去。

3 申请大小的限制不同

栈: 栈向低地址扩展,是一块连续的区域,栈顶的地址和栈的最大容量是系统规定好的

堆: 堆是低地址向高地址扩展,不连续的内存区域

4 申请效率不同

栈由系统自动分配,速度较快,程序员无法控制

堆是由new分配、速度慢,容易产生碎片,但是方便

5 堆栈存储内容不同

栈: 函数调用时,第一个进栈的是主函数中的下一条指令,然后是各个参数。

堆: 一般在堆的头部用一个字节存放大小,堆中的内容程序员安排