C开发编译与调试
Posted mChenys
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C开发编译与调试相关的知识,希望对你有一定的参考价值。
目录
一、gcc编译流程
1.1 预处理阶段
宏定义展开,宏定义替换,展开include的文件
gcc -E -o hello.i hello.c
例如源文件hello.c内容如下:
#include <stdio.h>
int main()
printf("hello test");
return 0;
经过预处理后生成的hello.i内容如下:
# 1 "hello.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 391 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "hello.c" 2
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/stdio.h" 1 3 4
# 64 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/stdio.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_stdio.h" 1 3 4
# 68 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_stdio.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h" 1 3 4
# 649 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/_symbol_aliasing.h" 1 3 4
# 650 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h" 2 3 4
# 715 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/_posix_availability.h" 1 3 4
# 716 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h" 2 3 4
# 69 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_stdio.h" 2 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 1 3 4
# 135 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityVersions.h" 1 3 4
# 136 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 2 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternal.h" 1 3 4
# 137 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 2 3 4
# 70 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_stdio.h" 2 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_types.h" 1 3 4
# 27 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_types.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/_types.h" 1 3 4
# 33 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/_types.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/machine/_types.h" 1 3 4
# 34 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/machine/_types.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/arm/_types.h" 1 3 4
# 13 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/arm/_types.h" 3 4
typedef signed char __int8_t;
typedef unsigned char __uint8_t;
typedef short __int16_t;
typedef unsigned short __uint16_t;
typedef int __int32_t;
typedef unsigned int __uint32_t;
typedef long long __int64_t;
typedef unsigned long long __uint64_t;
///内容太长了....这里省略....
typedef long __darwin_intptr_t;
typedef unsigned int __darwin_natural_t;
# 46 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/arm/_types.h" 3 4
typedef int __darwin_ct_rune_t;
# 2 "hello.c" 2
int main()
printf("hello test");
return 0;
可以看到预处理阶段后,源码的内容变多了,因为这里面包含了宏定义和include的文件的内容
1.2 预编译阶段
在这个阶段,gcc才会去检测代码的规范,语法是否有错误,gcc会把代码翻译成汇编
gcc -S -o hello.s hello.i
生成的汇编代码在hello.s文件内
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 11, 0 sdk_version 11, 3
.globl _main ; -- Begin function main
.p2align 2
_main: ; @main
.cfi_startproc
; %bb.0:
sub sp, sp, #32 ; =32
stp x29, x30, [sp, #16] ; 16-byte Folded Spill
add x29, sp, #16 ; =16
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
mov w8, #0
str w8, [sp, #8] ; 4-byte Folded Spill
stur wzr, [x29, #-4]
adrp x0, l_.str@PAGE
add x0, x0, l_.str@PAGEOFF
bl _printf
ldr w0, [sp, #8] ; 4-byte Folded Reload
ldp x29, x30, [sp, #16] ; 16-byte Folded Reload
add sp, sp, #32 ; =32
ret
.cfi_endproc
; -- End function
.section __TEXT,__cstring,cstring_literals
l_.str: ; @.str
.asciz "hello test"
.subsections_via_symbols
1.3 汇编阶段(最耗时)
把.s文件翻译成二进制.o文件(机器指令)
gcc -c -o hello.o hello.s
1.4 链接阶段
计算逻辑地址,合并数据段,有些函数是在另外一个so库文件中的
gcc -o hello hello.o
经过这一步之后就会生成可执行文件hello了.然后在控制台上输入./hello就可以执行了.
通常用的比较多的就是-o指定生成可执行文件的名字, 例如
gcc -o hello hello.c
就可以生成可执行文件hello了
二、gcc的相关参数
- -I参数
-I (大写的i):用于指定当前的.c文件用到的.h文件所在的目录,这样就不会报找不到头文件的错误了.
例如某个目录下有一个a.h文件
#pragma once
int sum(int a,int b); //声明sum方法
然后hello.c文件如下:
#include<stdio.h>
#include "a.h" //包含a.h头文件
int main()
int ret = sum(1,2);
printf("ret=%d\\n",ret);
return 0;
int sum(int a,int b) //实现sum方法
return a+b;
如果直接用gcc hello.c会报这个错误
然后使用-I指定a.h的所在的目录
gcc hello.c -I ../12/
这样编译就OK了
-
-o选项
用于指定编译后生成的文件名称,不指定的话默认是a.out -
-D选项
通过-D可以在编译的时候设置宏定义
例如hello.c的代码如下:
#include<stdio.h>
#include "a.h"
int main()
int ret = sum(1,2);
#ifdef DEBUG
printf("hello world\\n");
#endif
printf("ret=%d\\n",ret);
return 0;
int sum(int a,int b)
return a+b;
上面代码的意思就是如果存在DEBUG宏,那么就会多打印hello world
通过下面命令可以编译的时候添加宏定义
-
-L选项
用于指定包含库的路径 -
-l选项(小写的L)
用于指定库的名(通常是libxxx.so或者libxxx.a 使用时是-lxxx) -
-g选项
用于gdb调试,不加此选项不能调试 -
-Wall选项
用于显示更多提示 -
-lstdc++选项
用于编译c++文件,当然c++文件一般用g++编译. -
-O选项
优化代码的选项,有1-3级别,例如-O1表示用1级别优化 -
-c选项
将.c文件或.s汇编文件编译生成.o二进制文件 -
-fPIC选项
将.c文件编译成与位置无关的代码的.o文件 (PIC是Position Independent Code的简写) -
-shared
将与位置无关代码的.o文件打包成.so动态库文件.
三、Linux下静态库的制作和使用
3.1 制作静态库
静态库的命名规范为:libXxx.a 对应Windows的.lib文件.
制作步骤:
a.通过gcc -c选项将.c文件编译成.o文件
b.使用ar rcs 命令将.o文件打包,使用命令:
ar rcs libxxx.a file1.o file2.o ...
假设当前工程的.c和.h文件结构如下
其中a,b,d,e的c文件都包含了include目录下的.h文件,那么打包静态库的步骤如下:
首先,将所有的.c文件编译成.o文件
用-I(大写的i)指定头文件的路径,用*.c代替src目录下的所有.c文件, 当然你不嫌麻烦的话也可以逐个进行.o文件的编译.然后,将所有的.o文件进行打包,库名叫做libCalc.a
同样,如果你不嫌麻烦也可以这样打包: ar rcs libCalc.a a.o b.o d.o e.o
如何查看.a库文件的内容呢?
通过nm命令可以查看.a文件内包含了哪些.o文件,以及用到了什么函数,例如:
3.2 使用静态库
将上面步骤生层的libCalc.a文件移动到工程的lib目录内,同时在工程的根目录下创建main.c文件,内容如下:
#include <stdio.h>
#include "head.h"
int main()
int ret1 = add(10,10);
int ret2 = mul(10,10);
int ret3 = div(10,10);
int ret4 = sub(10,10);
printf("10+10=%d\\n",ret1);
printf("10*10=%d\\n",ret2);
printf("10/10=%d\\n",ret3);
printf("10-10=%d\\n",ret4);
return 0;
此时的工程目录结构如下:
然后编译main.c文件的时候需要输入下面命令
gcc -o main main.c -I ./include/ -L ./lib/ -lCalc
然后输入./main就可以运行程序了.
ps:使用-l的时候,格式是固定的,库文件需要省略前缀lib和后缀.a,并且需要和-l(小写的L)连着写.
四、Linux下动态库的制作和使用
4.1 制作动态库
动态库的命名规范为:libXxx.so 对应Windows的.dll文件.
制作步骤:
a.通过gcc -c -fPIC 将.c文件编译与位置无关的代码的.o文件,关键参数 -fPIC (PIC是Position Independent Code的简写)
b.通过gcc -shared -o将位置无关的.o文件打包.so文件,so文件的命名格式:libXxx.so
ps:上面2个步骤也可以合成一个步骤,就是 -fPIC 和 -shared 一起使用
例如还是上面那个工程
下面开始制作动态库
首先,将.c文件生成位置无关代码的.o文件,进入src目录,使用下面命令
gcc -c *.c -fPIC -I ../include/
然后,将所有.o文件打包成一个.so文件,使用下面命令
gcc -shared -o libCalc.so *.o
然后将生成的libCalc.so文件拷贝到当前工程的lib目录内, 然后在工程根目录,使用下面命令进行编译生成可执行程序
gcc -o main main.c -I include/ -L lib/ -lCalc
此时的项目结构:
现在你尝试./main运行会发现运行出错,并且使用ldd命令查看main会发现libCalc.so文件找不到
这时就需要介绍如何使用动态库了
4.2 使用动态库
方式一,将libCalc.so文件直接拷贝到系统的/lib或者/usr/lib目录内,或者创建软连接(不推荐)
方式二,将库路径添加到环境变量 LD_LIBRARY_PATH中(这种方式是临时的,下次登录就没了,不推荐),使用如下命令添加
export LD_LIBRARY_PATH=/home/chenys/work/WorkSpace/C/14/day01/lib:$LD_LIBRARY_PATH
当然这种方式还可以将配置添加到home目录下的.bashrc文件中,这样就是永久的.
方式三,配置/etc/ld.so.conf文件,在文件末尾增加库路径,例如/home/chenys/work/WorkSpace/C/14/day01/lib
(ps:路径随你定,只要有.so文件存在这个路径即可.)
然后执行sudo ldconfig -v刷新下配置.
配置完成后,就可以运行main程序了,并且用ldd命令也能查看到libCalc.so正确找到了.
五、makefile的编写
makefile的好处是一次编写,终身受益
a.命名规则:makefile 或者 Makefile
b.三要素: 目标,依赖,规则, 其中目标是必须的,其它2个非必须.
c.写法:
目标:依赖
tab键规则命令
d.如何执行makefile文件:
控制台输入make [目标] ,如果不写目标,那么默认执行makefile的第一个目标,带上目标后则执行指定的目标
5.1 创建makefile生成模板
除了手动创建makefile文件外,我们还可以拷贝makefile的模板来生成,为了操作方便,我这里通过在~/.bashrc文件中添加一个alias命令来简化这一过程,添加如下语句到~/.bashrc
文件中:
alias echomake='cat ~/template/makefile.template >> makefile'
注意等号2边不要留有空白符,然后在创建~/template/makefile.template文件
SrcFiles=$(wildcard *.c)
TargetFiles=$(patsubst %.c,%,$(SrcFiles))
all:$(TargetFiles)
%:%.c
gcc -o $@ $< -g
.PHONY:clean
clean:
-@rm -rf $(TargetFiles)
最后在控制台输入echomake就可以自动使用模板创建一个makefile了
5.2 使用演示
假设工程的目录结构如下
其中head.h的内容如下:
int add(int a,int b);
int mul(int a,int b);
int div(int a,int b);
int sub(int a,int b);
a.c、b.c、d.c、e.c的内容如下:
//a.c
#include "../include/head.h"
int add(int a,int b)
return a+b;
//b.c
#include "../include/head.h"
int mul(int a,int b)
return a+b;
//d.c
#include "../include/head.h"
int div(int a,int b)
return a+b;
//e.c
#include "../include/head.h"
int sub(int a,int b)
return a+b;
main.c内容如下:
#include <stdio.h>
#include "include/head.h"
int main(int argc,char* args[])
if(argc >1)
for(int i=1;i<argc;i++)
printf("第%d个参数=%s\\n",i,args[i]);
int ret1 = add(10,10);
int ret2 = mul(10,10);
int ret3 = div(10,10);
int ret4 = sub(10,10);
printf("10+10=%d\\n",ret1);
printf("10*10=%d\\n",ret2);
printf("10/10=%d\\n",ret3);
printf("10-10=%d\\n",ret4);
return 0;
然后在工程根目录下通过vi makefile
打开编辑文本,内容例如:
main:main.c ./src/a.c ./src/b.c ./src/d.c ./src/e.c
gcc -o main -I ./include main.c ./src/a.c ./src/b.c ./src/d.c ./src/e.c
注意命令前是一个tab键
保存退出后,输入make命令会执行makefile的内容.
可以看到make执行的内容,就是makefile定义的规则,同时在当前目录下生层了main可执行文件.
如果执行后看到Makefile:2:*** missing separator. Stop.
错误,那么很有可能是你的makefile文件在命令前的tab键格式不对,需要在vi编辑模式下按下tab,不要自己打空格.
上面这种编写方式的缺点是,如果更改其中一个文件,那么所有的源码都需要重新编译.
5.3 改进makefile文件的编写
考虑采用编译过程分解,先生成.o文件,然后使用.o文件得到结果.
修改项目结构,同时修改a.c、b.c、d.c、e.c里面的include路径
在项目根目录下重新创建一个makefile文件
main:main.o a.o b.o d.o e.o
gcc -o main -I ./include main.o a.o b.o d.o e.o
main.o:main.c
gcc -c main.c -I ./include
a.o:a.c
gcc -c a.c -I ./include
b.o:b.c
gcc -c b.c -I ./include
d.o:d.c
gcc -c d.c -I ./include
e.o:e.c
gcc -c e.c -I ./include
保存后,输入make,等到下面结果
执行main,得到结果.
这样看似和之前的没啥区别, 但是当我们修改a.c代码后, 再次执行make命令,你会发现输出的命令变少了
可以看到仅输出了2条,第一条是生成a.o的; 第2条是生成main可执行程序的, 这样就实现了增量编译
了.效率也得到了提升.
六、makefile的语法
-
1)定义变量
变量名=值 -
2)注释
#号表示注释 -
3)使用变量
$(变量名)
例如:
#定义一个变量,存放依赖的路径
objFile = main.o a.o b.o d.o e.o
#通过&(变量名)来使用变量
main:$(objFile)
gcc -o main -I ./include main.o a.o b.o d.o e.o
main.o:main.c
gcc -c main.c -I ./include
a.o:a.c
gcc -c a.c -I ./include
b.o:b.c
gcc -c b.c -I ./include
d.o:d.c
gcc -c d.c -I ./include
e.o:e.c
gcc -c e.c -I ./include
- 4)函数
wildcard:可以进行文件匹配,用法:$(wildcard 要查找的文件)
patsubst:内容替换(翻译:subst 字符串替换 . patsubst 带模式的字符串替换),用法:
$(patsubst 替换前的规则,替换后的规则,需要替换的内容)
例如:
#查找当前目录下所有的.c文件,保存到SrcFiles变量中
SrcFiles=$(wildcard *.c)
#将SrcFiles变量内的.c后缀修改成.o后缀,然后保存到ObjFiles变量中,这里用到了%通配符
ObjFiles=$(patsubst %.c,%.o,$(SrcFiles))
#main的依赖以及规则中用到的文件名就可以直接使用ObjFiles变量的内容了来写活了.
main:$(ObjFiles)
gcc -o main -I ./include $(ObjFiles)
main.o:main.c
gcc -c main.c -I ./include
a.o:a.c
gcc -c a.c -I ./include
b.o:b.c
gcc -c b.c -I ./include
d.o:d.c
gcc -c d.c -I ./include
e.o:e.c
gcc -c e.c -I ./include
- 5)模式推导和makefile变量结合使用
使用%号来代替符合一定规律的内容,相当于通配符, 通常会和makefile的变量一起使用,常用的makefile变量有:
$@ :代表目标
$^ :代表全部依赖
$< :以上是关于C开发编译与调试的主要内容,如果未能解决你的问题,请参考以下文章