gcc、g++编译的使用与区别【含vscode中简单的GDB调试】

本文详细介绍了C++编译的四个步骤,包括预处理、编译、汇编和链接,并通过实例展示了gcc和g++的使用。讨论了动态库与静态库的区别,包括它们在程序执行和空间占用上的差异。此外,还讲解了在Linux环境下如何添加共享库路径的三种方法,以及g++的重要编译参数,如生成调试信息和代码优化。最后,提到了在Windows系统下使用GDB工具进行C++程序的调试方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 了解编译过程

  • 步骤一:预处理(预编译):编译处理宏定义等宏命令———生成后缀为“.i”的文件
  • 步骤二:编译:将预处理后的文件转换成汇编语言———生成后缀为“.s”的文件
  • 步骤三:汇编:由汇编生成的文件翻译为二进制目标文件———生成后缀为“.o”的文件
  • 步骤四:连接:多个目标文件(二进制)结合库函数等综合成的能直接独立执行的执行文件———生成后缀为“.out”的文件

在这里插入图片描述

1.1 例子:

使用 g++ 编译C++源代码的时候,我们可使用以下命令 即可完成编译C++源代码文件,并且直接产生可执行的二进制文件

# 编译test.cpp 文件,在 Linux 下,默认产生名为 a.out 的二进制可执行文件
g++ test.cpp

实际上,上面的一步编译指令包含了以下几个过程:
在这里插入图片描述

第一步:预处理 Pre-processing,生成.i 文件

# -E 选项指示编译器仅对输入文件进行预编译
g++ -E test.cpp -o testr.i

预处理:查找头文件,展开宏定义

不会检查语法错误

第二步:编译-Compiling,生成.s 文件

# -S 编译选项告诉 g++ 在为 c++ 代码产生了汇编语言文件后停止编译
# g++ 产生的汇编语言文件的缺省扩展名是 .s
g++ -S test.i -o test.s

会检查语法错误

第三步:汇编-Assembing,生成.o 文件

# -c 选项告诉 g++ 仅把源代码编译为机器语言的目标代码
# 缺省时 g++ 建立的目标代码文件有一个 .o 的扩展名
g++ -c test.s -o test.o

第四步:链接-Lingking,生成 bin 二进制文件

# -o 编译选项来为将产生的可执行文件指定文件名,如果不使用-o参数,在Linux下默认输出名为 a.out 的可执行文件
g++ test.o -o test
  • ps:VSCode 是通过调用 GCC 编译器来实现 C/C++的编译工作的。
  • ps:在日常交流中通常使用“编译”统称这 4 个步骤,如果不是特指这 4 个步骤中的某一个

2. 了解动态库与静态库

2.1动态库与静态库的区别

  • 根据链接时期的不同,库又有静态库和动态库之分。静态库是在链接阶段被链接的,所以生成的可执行文件就不受库的影响,即使库被删除,程序依然可以成功运行。而动态库是在程序执行的时候被链接的。程序执行完,库仍需保留在系统上,以供程序运行时调用。
  • 链接静态库从某种意义上来说是一种复制粘贴,被链接后库就直接嵌入可执行程序中了,这样系统空间有很大的浪费,而且一旦发现系统中有bug,就必须一一把链接该库的程序找出来,然后重新编译,十分麻烦。而动态库刚好弥补了这个缺陷,因为动态库是在程序运行时被链接的,所以磁盘上只需保留一份副本,一次节约了空间,如果发现bug或者是要升级,只要用新的库把原来的替换掉就可以了。
  • 静态库是不是一无是处了呢?非也。如果代码在其他系统上运行,且没有相应的库时,解决办法就是使用静态库。而且由于动态库是在程序运行的时候被链接,因此动态库的运行速度比较慢。
  • 静态库的扩展名一般为“.a”或“.lib”;动态库的扩展名一般为“.so”或“.dll”。

3. gcc与g++的一些区别:

  • gcc无法进行库文件的连接;而g++则能完整编译出可执行文件。(实质上,g++从步骤1-步骤3均是调用gcc完成,步骤4连接则由自己完成)

  • 对于 .c和.cpp文件,使用gcc编译会分别当做c和cpp文件编译(cpp的语法规则比c的更强一些)

  • 对于 .c和.cpp文件,使用g++则统一当做cpp文件编译

  • 在编译c++代码时,g++会调用gcc,所以两者是等价的,但是因为gcc命令不能自动和C++程序使用的库链接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接都用g++,这就给人一种错觉,好像cpp程序只能用g++似的。

  • gcc在编译.c文件时,可使用的预定义宏是比较少的,很多都是未定义的。 gcc在编译cpp文件时、以及g++在编译c文件和cpp文件时(这时候gcc和g++调用的都是cpp文件的编译器),会加入一些额外的宏,这些宏如下:

    #define __GXX_WEAK__ 1
    #define __cplusplus 1
    #define __DEPRECATED 1
    #define __GNUG__ 4
    #define __EXCEPTIONS 1
    #define __private_extern__ extern
    

    所以,既然使用g++编译时,会统一当做cpp文件编译,那么为了对c编译的支持,利用默认的宏定义__cplusplus,在c代码中添加如下内容。

    #ifdef __cplusplus 
    extern "C" { 
    #endif
    
    //一段c代码
    
    #ifdef __cplusplus 
    } 
    #endif 
    

4. gcc使用

4.1 语法

gcc [options] file

4.2 常用编译参数

编译参数详细说明
-oObject的简写,指定编译的⽬标文件名,否则会⽣成的⽬标⽂件名是a.out;eg: gcc main.c -o main
-S把源文件编译成汇编代码
-E只执行预处理
-c把预处理、编译、汇编都做了,但是不链接,例如:gcc -c -o main.o main.c
-include包含头文件,功能如同在源码的语句。eg:#include <xxx.h>
-I (大写i)include简写,指定程序包含头文件的路径,一般用于指定第三方库的头文件。默认在/usr/include,没有就要用 -I 参数指定了
-L编译时,用于指定程序第三方库的查找路径。
-l链接时,指定程序需要进行链接的库。注:一般库文件名是libxxx.so,-I指定xxx即可。如-Ixxx在/lib和/usr/lib和usr/local/lib里面的库直接用-l参数就能链接,如果库文件没有放在系统库目录(又称为编译工具链目录)中,需要使用-L 参数(大写l)指定库文件所在目录
-rpath程序执⾏需要指定动态库的路径,但是可以⽤-rpath参数在编译时指定程序运⾏时需要加载的库的路径。
-D程序编译阶段可以定义一些宏,该方法可以让程序有选择性的运行代码。
-0n这是程序的优化等级。n的范围是0-3。n越大优化等级越高,程序运行的越快。否则越慢,n==0时是关闭优化。利于程序的调试,一般程序调试阶段会关闭优化等级,发布程序会把优化等级设为-O2。-O0:不进行优化处理。-O 或 -O1:优化生成代码。-O2:进一步优化。-O3 比 -O2 更进一步优化,包括 inline 函数。
-g打印程序的调试信息,如果需要使⽤gdb⼯具进⾏调试程序,程序编译的时候,需要加上该参数。
-share编译的时候尽量使用动态库。(除非只有静态库,没有动态库)
-static禁止使用动态库,编译的时候只加载静态库,这会导致执行件很大。
-w不生成任何的警告信息。
-Wall生成所有的警告信息。
-fpic使输出的对象模块可重定位地址方式行成的。
-shared把对应的源文件形成对应的动态链接库。
-std=c++11设置编译标准

编译时候加上-v,会输出编译过程,可以排查错误,检查头文件的路径是否正确,注意头文件的作用有两个:

  1. 编译工具会根据头文件判断函数的实现是否有问题;
  2. 头文件的没有指定则是在系统目录(工具链目录)下定义

所以如果使用的是交叉编译工具链,则使用-v去查看头文件的系统路径会发现是在编译工具链下的某个include文件,而使用gcc时,头文件的系统路径是在usr下的某个include文件夹中

4.3 例子

只执行预处理,输出 hello.i 源文件

gcc -E hello.c -o hello.i

只执行预处理和编译,输出 hello.s 汇编文件

gcc -S hello.c

也可以由 hello.i 文件生成 hello.s 汇编文件

gcc -S hello.i -o hello.s

只执行预处理、编译和汇编,输出 hello.o 目标文件

gcc -c hello.c

也可以由 hello.i或hello.s生成目标文件hello.o

gcc -c hello.i -o hello.o
gcc -c hello.s -o hello.o

由目标文件hello.o链接成可执行文件 hello

gcc hello.o -o hello

使用-std设置编译标准

# 使用 c++11 标准编译 test.cpp
g++ -std=c++11 test.cpp

-D 定义宏,为了演示宏的作用,创建源码文件 gcc_02_test/test.cpp ,并添加以下C++源代码

#include <stdio.h> //尖括号表示在系统目录下查找,“”表示在用户指定目录下查找

int main()
{ 
    // 根据是否存在 DEBUG 进行逻辑处理
    #ifdef DEBUG
        printf("DEBUG LOG\n");
    #endif
        printf("in\n");
    return 0;
}

使用 g++ -DDEBUG test.cpp 编译的同时定义DEBUG宏,执行编译后的可执行文件可以看到 “DEBUG LOG” 被输出。原因是我们使用-DDEBUG 参数定义 DEBUG 宏,在执行程序的时候,程序检测到了 DEBUG 宏的存在,并执行了对应的逻辑。

4.4 生成、调用静态库

test.c

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

test.h

#ifndef _TEST_H_
#define _TEST_H_
extern int add(int a,int b);

main.c

#include<stduo.h>
#include<test.h>
int main()
{
int a,b;
	printf("please input a and b\n");
	scanf("%d %d",&a,&b);
	printf("The add:%d\n",add(a,b));
}

在此例中,test.c用于编译生成静态库libtest.a,test.h为libtest.a对应的头文件。

生成test.o目标文件

gcc -c test.c -o test.o

使用ar将test.o打包成libtest.a静态库

ar rcs -o libtest.a test.o

生成libtest.a静态库后,可以使用命令查看libtest.a文件中包含哪些文件

art libtest.a

知识点补充:

Linux 中 ar命令:
-功能:创建静态库.a文件 -指令参数
-d  删除静态库中的成员文件。
-m  变更成员文件在静态库中的次序。
-p  显示静态库中的成员文件内容。
-q  将文件附加在静态库的末端。
-r  将文件插入静态库中。
-t  显示静态库中所包含的文件。
-x  静态库中取出成员文件。

编译 main.c, 并链接静态库 libtest.a,生成的可执行文件名为app_static ,有如下两种方法:
方法一

gcc -o app_static main.c libtest.a

也可以加参数指定路径和库的名称

  • 链接时,-l参数后不加空格指定所需要链接的库名,这里库名是libtest.a,但是只需要给出-ltest即可,ld会以libtest作为库的实际名字。
  • 链接时,-L 指定库的搜索路径,.表示的当前路径

方法二

gcc -o app_static main.c -L. -ltest 

运行app_static 【静态库编译得到的文件可以直接执行,不需要把静态库放到目录中,而动态库则不行

$ ./app_static 

查看文件描述

$ file app_static 
hello: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=b72236c2211dd8f0c3003bc02ad5e70bb2354e8c, for GNU/Linux 3.2.0, not stripped

file:用来识别文件类型,也可用来辨别一些文件的编码格式。它是通过查看文件的头部信息来获取文件类型。

4.5 生成、调用共享库\动态库

生成test.o目标文件,-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

由于动态库可以被多个进程共享加载,所以需要使用-fPIC选项生成位置无关的代码

gcc -c -o test.o -fPIC test.c

使用-shared参数生成动态库

gcc -shared -o libmyshare.so test.o

上述两个命令可以连在一起

gcc -shared -fPIC -o libmyshare.so test.c

编译main.c,使用libmyshare.so动态库,命令如下两种都行

gcc -o app_share main.c -L. -lmyshare
或
gcc -o app_share main.c ./libmyshare.so

查看app_share使用动态库,

ldd app_share

ldd:用来打印或者查看程序运行所需的共享库(访问共享对象依赖关系),常用来解决程序因缺少某个库文件而不能运行的一些问题。

运行:关于动态库的使用,编译和运行是不同的,运行时也得指定动态库的路径

  • 如果libmyshare.so无法找到,直接执行./app_share就会出现错误。解决方法:下面有三种运行时添加共享库路径的方法

5. 运行时添加共享库路径的三种方式

5.1 方式一:设置环境变量 LD_LIBRARY_PATH

LD_LIBRARY_PATH是Linux环境变量名,该环境变量主要用于指定查找共享库(动态链接库)时,除了默认路径之外的其他路径。

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库的路径

将 libmyshare.so 所在的当前目录添加到 LD_LIBRARY_PATH 变量,再次执行 app_share

5.2 方式二:使用 rpath 将共享库位置嵌入到程序

gcc hello.c -L. -lfoo -Wl,-rpath=`pwd` -o hello

rpath 即 run path,是种可以将共享库位置嵌入程序中的方法,从而不用依赖于默认位置和环境变量。

这里在链接时使用 -Wl,-rpath=xxxx 选项,-Wl 会发送以逗号分隔的选项到链接器,注意逗号分隔符后面没有空格。pwd:打印当前目录

这种方式要求共享库必须有一个固定的安装路径,欠缺灵活性,不过如果设置了 LD_LIBRARY_PATH,程序加载时也是会到相应路径寻找共享库的。

5.3 方式三:将 libmyshare.so 共享库添加到系统路径

sudo cp libmyshare.so /usr/lib/

执行程序,如果程序仍然运行失败,请尝试执行 ldconfig 命令更新共享库的缓存列表。

此时,再次查看程序的共享库依赖

$ ldd app_share
linux-vdso.so.1 (0x00007ffecfbb1000)
libmyshare.so => /lib/libmyshare.so (0x00007f3f3f1ad000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3f3efbb000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3f3f1d6000)

可以看到 libmyshare.so已经被发现了,其中 /lib 是 /usr/lib 目录的软链接。

很有用的选项

gcc -E main.c // 查看预处理结果,比 如头文件是哪个
gcc -E -dM main.c > 1.txt // 把所有的宏展开,存在 1.txt 里
gcc -Wp,-MD,abc.dep -c -o main.o main.c // 生成依赖文件 abc.dep ,后面 Makefile 会用
echo 'main(){}'| gcc -E -v - // 它会列出头文件目录、库目录(LIBRARY_PATH)

6. g++使用

  • 用法与gcc基本无异,不同的是g++编译过程包含了编译和链接。

3. g++重要编译参数

3.1. 编译带调试信息的可执行文件

-g 选项告诉GCC产生能被 GNU调试器(DGB) 使用的调试信息,以调试程序

# 产生带调试信息的可执行文件test
g++ -g test.cpp -o test
3.2. 优化源代码

所谓优化,是指如省略代码中从来未使用过的变量、直接常量表达式用结果替代等操作,这些操作会缩减目标文件所含的代码,提高最终生成的可执行文件的运行效率。

O 参数告诉 g++ 对源代码进行基本优化。这些优化在大多数情况下都使程序执行得更快,常用优化级别如下:

-O: 同时减少代码的长度和执行时间,其效果等价于 -O1

-O0: 表示不做优化

-O1: 表示默认优化

-O2: 告诉 g++ 产生尽可能小和尽可能快的代码。除了完成-O1 的优化之外,还进行一些额外的调整工作,如指令调整等

-O3: 包括循环展开和其他一些与处理性相关的优化工作,选项将使编译的速度比 -O 慢,但通常产生的代码执行速度会更快。

# 使用 -O2 优化源代码,并输出可执行文件。
g++ -O2 test.cpp
3.3 使用time查看可执行文件运行时间

在这里插入图片描述

7. 在windows系统下使用GDB工具调试C++单文件程序

编译环境设置:windows安装minggw支持linux程序编译

  1. 创建c++文件

  2. 终端输入指令:g++ -g .\main.cpp

  3. 点击左侧的调试按钮:在这里插入图片描述

  4. 点击:在这里插入图片描述

  5. 点击:在这里插入图片描述

  6. 进入调试界面:快捷键F9是设置短点,F10是设置下一步,

  7. F5是设置运行,或者按下设置运行:在这里插入图片描述

  8. launch.json中最重要的信息:【第一个生成可调试的可执行文件,和main.cpp同一个路径下,第二个在编译前指定要执行task.exe任务,在.vscode路径下】在这里插入图片描述

8. 在windows系统下使用GDB工具调试C++多文件程序

采用上述单文件的操作会报错,需要手动写launch.json和task.json

  1. 修改launch.json文件:在这里插入图片描述
  2. 注释掉preLaunchTask

参考博文:

树莓派——Linux共享库、静态库、动态库详解
静态库和动态库的区别
gcc与g++

### 配置 VSCode 使用 GCC 编译器 要在 Visual Studio Code (VSCode) 中配置 `gcc.exe` 来编译调试 C/C++ 程序,需要完成以下几个方面的设置: #### 1. 安装必要的工具链 确保已安装 MinGW 或其他支持 GCC 的工具链。MinGW 提供了 Windows 平台上的 GNU 工具集,包括 `gcc.exe` 和 `g++.exe`。 可以通过以下方式验证是否成功安装: ```bash gcc --version g++ --version ``` 如果返回版本号,则说明安装成功[^2]。 --- #### 2. 创建并编辑 `tasks.json` 此文件用于定义编译任务。通过快捷键 `Ctrl+Shift+P` 打开命令面板,输入 **Tasks: Configure Task**,选择创建一个新的任务文件。 以下是基于 GCC 的典型 `tasks.json` 文件内容: ```json { "version": "2.0.0", "tasks": [ { "label": "build hello.cpp", "type": "shell", "command": "g++", "args": [ "-g", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}" ], "group": { "kind": "build", "isDefault": true }, "problemMatcher": ["$gcc"] } ] } ``` 上述配置会调用 `g++` 对当前活动文件进行编译,并生成可执行文件[^3]。 --- #### 3. 配置 `launch.json` 为了实现程序的调试功能,需创建或更新 `.vscode/launch.json` 文件。该文件指定了调试器的行为参数。 示例配置如下: ```json { "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", "program": "${fileDirname}/${fileBasenameNoExtension}.exe", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "gdb", "miDebuggerPath": "C:/path/to/mingw/bin/gdb.exe", // 替换为实际 gdb 路径 "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ], "preLaunchTask": "build hello.cpp" } ] } ``` 注意:将 `"miDebuggerPath"` 设置为您本地 GDB 可执行文件的实际路径。 --- #### 4. 更新 `c_cpp_properties.json` 为了让 IntelliSense 正确解析头文件路径和其他编译选项,应调整 `.vscode/c_cpp_properties.json` 文件的内容。 示例如下: ```json { "configurations": [ { "name": "Win32", "includePath": [ "${workspaceFolder}/**", "C:/path/to/mingw/include" // 添加您的 MinGW 头文件路径 ], "defines": [ "_DEBUG", "UNICODE", "_UNICODE" ], "compilerPath": "C:/path/to/mingw/bin/gcc.exe", // 指定 gcc.exe 的位置 "cStandard": "c11", "cppStandard": "c++17", "intelliSenseMode": "clang-x64" } ], "version": 4 } ``` 此处的关键字段是 `compilerPath` 和 `includePath`,它们分别指向 GCC/G++ 的二进制文件以及标准库头文件的位置。 --- #### 测试流程 编写一个简单的测试程序(如 `hello.cpp`),保存后按下 `F5` 启动调试模式。如果没有错误提示,应该能够看到控制台输出 “hello, world!”。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值