百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

解读gcc和g++编译器分别对c与c++文件影响

bigegpt 2024-09-05 12:04 3 浏览

概述

为什么需要解读gcc/g++编译器对c/c++文件的影响呢?由于系统内核一般是使用C语言来编写的,系统内核中用C语言实现了很多库。而上层应用程序有可能是用C++来开发,如果在内核库函数头文件中不用extern“C”来声明库函数的话,在编写C++应用程序时,包含库头文件,在C++文件链接时就会以C++标准来链接库的函数名,而在库文件实现时是用C来实现的,二者函数名不同,在链接时就会出现找不到函数的现象。

实验环境

本次实验平台:Debian 9.4 gcc/g++版本:6.3.0 IDE环境:Qt Creator 4.2.0 qmake版本:3.0。这里可以看到最新的Debian系统对软件版本更新也是很及时的,对于电脑不能上外网情况,debian与centos都推出了离线包,两个系统都使用了很长一段时间,这里还是给大家推荐centos,centos很多东西是继(chao)承(xi)红帽的,可能是这个原因,所以centos很稳定、bug少。

编译步骤

编译器一般可分为预处理、编译、汇编、链接四个阶段,GNU手册也做了介绍,手册如图 1所示。

图 1

预处理:将宏定义展开,将相关和类型定义引入等操作,生成后缀为.i的预处理文件。

编译:将预处理文件编译成汇编文件,生成后缀.S的汇编文件。

汇编:将汇编文件编译成目标文件,生成后缀为.o的目标文件。

链接:将各个.o文件与相关库文件进行链接,链接方式可以选择静态或动态链接。

上面是标准的面经,同时也是网上最容易搜索到的答案,但实质根本没理解gcc/g++编译。从上述面经来看,感觉就是gcc只能编译c程序,g++只能编译c++程序。gcc/g++编译c/c++程序可以分为四种情况,gcc编译.c程序,gcc编译.cpp程序,g++编译.c程序,g++编译.cpp程序。下面通过对比这四种情况来真正了解gcc/g++编译器。

gcc/g++编译.c文件

为了彻底理解清楚两种之间的区别,下面通过编译的几个阶段去解读,test.c文件如图 2所示,这里需要注意,在test.c中我没有引入c++头文件和语法,如果用gcc编译,那么就连万里长征第一步预编译都不能通过,有且仅有当用gcc编译.c文件时,编译器才会按照C文件规则进行预编译,这里提前提出结论。为了更好的对比,对比.c文件代码中采用c规则。

图 2

预编译处理

使用gcc和g++分别对test.c进行预处理,执行结果如图 3所示。

图 3

从图中可以看到24c24等字符,c代表转换意思,前后数字代表行数,前后出现一对数字代表行号区间。同时可以看到g++预编译.c文件时候,会在部分头文件和类型定义前添加extern “C”,这个标识符的作用把标识符作用域的数据类型采用gcc去编译,C++保留了一部分过程式语言的特点(被世人称为“不彻底地面向对象”),因而它可以定义不属于任何类的全局变量和函数。但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与C有明显的不同。

编译阶段

编译阶段是把.i文件编译成汇编文件,编译过程和结果如图 4所示。

图 4

通过图中对比发现,只有程序中自定义的函数接口命中不一样,其它代码完全一样。gcc编译器编译后的函数名字和编译前函数名一致(有些可能是在函数前面添加_,具体机制是跟编译机制有关),g++编译器是在函数中添加_z3*ii。这里其它代码相同原因是程序中使用的语法c++是部分兼容c的,上图仅仅代表是一种特殊情况,因为.c文件中全部采用的c语法规则。

汇编阶段

程序处于汇编阶段时,gcc/g++内部都是调用as汇编命令,在这里两者是没有区别的,可以通过如下命令做测试,用c++_test.S做测试对比。

gcc -c c++_test.S -o c_test.o

g++ -c c++_test.S -o c++_test.o

diff c_test.o c++_test.o

通过对比试验,两个.o文件完全一样,在汇编阶段gcc/g++的作用是一样的,读者可以尽情做实验。

链接阶段

gcc/g++都是调用ld命令,同样两种区别在于传入的参数与调用库的不同,g++默认与c++库链接(兼容C语言部分,还是调用C库),gcc默认是与c库链接。实验是检验真理的最好工具,下面同样选取c++_test.o做实验。

gcc c++_test.o -o c_test

g++ c++_test.o -o c++_test

diff c_test c++_test

通过diff对比,两个可执行文件内容不一样的。通过strace跟踪系统调用结果如图 5所示。可以清晰看到两者仅仅是一些mmap地址映射不一样,即使两次用g++编译相同文件,可能生成的文件也不一样,这个不一样仅仅体现在地址映射上。

图 5

gcc/g++编译.cpp文件

编译cpp文件同样通过四个阶段做比对,同时还和编译.c文件得出的结论做对比,test.cpp文件内容如图 6所示。

图 6

复制test.cpp文件为test.c文件,然后进行四个阶段做对比。

预编译

g++ -E test.c -o test.ii

g++ -E test.cpp -o c++_test.ii

gcc -E test.cpp -o c_test.i

这里保存为.ii或者.i都是无所谓的,只有当预编译的输出需要手动指定作为编译阶段的输入(-x选项指定输入文件类型),并且没有指定编译阶段输入是什么类型文件,那么编译器就会根据系统自定义的后缀去解析,默认编译器会把.i后缀当成c预编译的结果,.ii后缀当成是c++预编译的结果。通过对比这个3个.i文件发现预编译结果完全一样,只有文件名不同,test.c与test.cpp的区别。

编译

gcc -S test.cpp -o c_test.S

g++ -S test.cpp -o c++_test.S

g++ -S test.c -o test.S

编译结果如图 7所示。生成的.S文件内容又是完全一样,只有.file不一样,这个标识符是指文件名,1c1代表第一个文件的第一行与第二个文件的第一行转换关系。

图 7

汇编

gcc -c test.cpp -o c_test.o

g++ -c test.cpp -o c++_test.o

g++ -c test.c -o test.o

汇编阶段结果同样是一致。

链接

g++ test.cpp -o c++_test

g++ test.c -o test

objdump -S test > test.obj

objdump -S c++_test > c++_test.obj

这两种编译结果通过查看二进制只有文件名是不一样的,两种方式都装载了libstdc++.so.6和libc.so.6库文件,这是g++兼容c的另外一个表现;同样可以通过反编译手段对比两个二进制文件,对比结果如图 8所示,两个obj文件同样只有文件名不一样。

图 8

还剩下一种情况没有对比,那就是gcc链接.cpp文件。

gcc c_test.o -o c_test

链接不能正常通过的,提示找不到c++库。由于链接阶段gcc不能够自动链接c++库,g++可以自动链接c++库,如果加上-lstdc++选项就能够正常编译通过。

gcc c_test.o -o c_test -lstdc++

同样可以对比c_test 与c++_test文件,但是读者会发现,这两个文件肯定不一样。我们通过反编译手段观察两个文件的区别,同样仅仅是地址不一样,比如_init地址不一样等。图 9是我截取的局部反编译对比图。程序最先从_init中rsp堆栈指针顶开始做堆栈处理,然后跳转到程序中我们看到的main函数入口地址,进入函数之前先保存栈指针,在进行push,程序中在跳转到add函数的的地址,最后程序执行完成在_fini中恢复栈平衡。(大概思路是这样的,已经三年没有碰过底层地址相关的知识,凭记忆补充一点相关知识。在嵌入式开发过程中有几个地方需要用到地址概念,uboot链接脚本确定程序第一条指令入口、编译文件顺序、位置无关地址跳转问题,栈顶设置;加载linux地址时分析相对复杂一点,编译kernel需要指定入uImage或zImage入口地址,uImage与zImage仅仅相差64Bytes头,这里牵扯到解压kernel,代码搬运等操作)。

图 9

通过上面两个对比实现可以得出如下结论:

后缀为.c的,gcc把它当成c程序;g++当作c++程序,即跟.cpp没有区别。

后缀.cpp的,gcc与g++都当成c++程序。

gcc不能自动链接c++库,g++会自动链接c++库。

c++程序中,预编译后会存在extern “C”标识符,即是用gcc按照c程序规则编译,这是兼容C程序的表现。

gcc也可以编译c++程序。

gcc编译.c文件,.c文件中一定不能出现c++语法和库操作。

__cplusplus测试

如果编译器按照c++规则编译,那么这个宏__cplusplus就会被编译器定义。上面已经得出结论了,下面通过这个宏定义再次验证上面1、2结论。test.c与test.cpp文件内容如图 10所示,依次执行如下命令。

gcc test.c && ./a.out @结果是:a+b=1314

g++ test.c && ./a.out @结果是:a+b=520

gcc test.cpp && ./a.out @结果是:a+b=520

g++ test.cpp && ./a.out @结果是:a+b=520

从结果同样可以证明,只有当用gcc编译.c程序时才会按照c规则编译程序。

图 10

有读者可能就问了,既然g++能够编译c程序,gcc还有必须要存在吗?这个答案是肯定的。原因一:操作系统全是按照c规则编写与编译(除开head.S等文件中的汇编),很多系统库文件都是按照c规则写与编译的,如果采用g++去编译这一部分,那么生成的可执行文件会非常大,以及程序运行效率大大降低;还可能存在不能正常编译通过情况,就是上述对比实验中函数链接接口不一样。编译器优化性能也是有限的,系统内核是整个上层应用的心脏,必须做到最高效率,不许存在过多冗余代码(编译器的问题)。原因二:做单片机出身的工程师对c语言扣字节操作应该是一种信仰,做上层应用的工程师更偏向c++面向对象,一个大项目基本都是通力合作完成。所以gcc用来编译c程序(效率问题,c实现对系统接口二次封装),g++编译c++程序。

工程包含.c与.cpp文件如何处理

如果是工程师自己写编译规则,那么在Makefile中应该检测.c和.cpp文件分别用gcc与g++编译,最后统一链接。如果是IDE环境,由IDE环境决定,在qt中qmake是将.c文件用gcc编译,g++编译。那么g++如何调用c程序封装的函数或者库呢,从上述讲解知识可以看到c++程序直接调用c程序接口,这个肯定报错提示找不到库函数,c和c++处理函数接口机制不一样,所以在c++程序中调用c程序应该在函数声明前加上extern “C”,这是在提醒g++编译链接这个函数时按照c函数机制去链接。同样如果在程序中函数实体前加extern “C”,这是在提醒编译器整个函数用gcc按照C规则去编译。切记理解编译与链接是两码事情。

图 11是一个QT工程模板,图中pcie.c文件的后缀只能用.c,如果换成.cpp,无论你是用gcc还是g++,走到链接阶段是找不到libpci.so提供的库函数的,虽然可以用把标准头文件路径pci.h中使用的函数声明前重新加上extern “C”,但是得不偿失。定义成.c文件qt环境会用gcc去编译,在pcie.h文件中的声明函数前加上extern “C”就可以正常链接。

图 11

动态库封装

看3条简单命令就可以很好理解,封库大多数是针对客户做二次开发使用;还有可能各个部门之间的合作需要封库。还有一种比较恶心的人,写代码特别喜欢封装库,玩隐蔽,不可替代性;在svn上只看得到几行画蛇添足的代码,在代码中调用库、管道接口、二进制文件。这种代码可阅读性很差,仅限本人阅读。

gcc -fPIC –shared test.c –o libtest.so //封库

gcc main.c –L. -ltest –o main //使用

export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

---------------------

作者:HeroKern

原文:https://blog.csdn.net/qq_21792169/article/details/85097822

相关推荐

当Frida来“敲”门(frida是什么)

0x1渗透测试瓶颈目前,碰到越来越多的大客户都会将核心资产业务集中在统一的APP上,或者对自己比较重要的APP,如自己的主业务,办公APP进行加壳,流量加密,投入了很多精力在移动端的防护上。而现在挖...

服务端性能测试实战3-性能测试脚本开发

前言在前面的两篇文章中,我们分别介绍了性能测试的理论知识以及性能测试计划制定,本篇文章将重点介绍性能测试脚本开发。脚本开发将分为两个阶段:阶段一:了解各个接口的入参、出参,使用Python代码模拟前端...

Springboot整合Apache Ftpserver拓展功能及业务讲解(三)

今日分享每天分享技术实战干货,技术在于积累和收藏,希望可以帮助到您,同时也希望获得您的支持和关注。架构开源地址:https://gitee.com/msxyspringboot整合Ftpserver参...

Linux和Windows下:Python Crypto模块安装方式区别

一、Linux环境下:fromCrypto.SignatureimportPKCS1_v1_5如果导包报错:ImportError:Nomodulenamed'Crypt...

Python 3 加密简介(python des加密解密)

Python3的标准库中是没多少用来解决加密的,不过却有用于处理哈希的库。在这里我们会对其进行一个简单的介绍,但重点会放在两个第三方的软件包:PyCrypto和cryptography上,我...

怎样从零开始编译一个魔兽世界开源服务端Windows

第二章:编译和安装我是艾西,上期我们讲述到编译一个魔兽世界开源服务端环境准备,那么今天跟大家聊聊怎么编译和安装我们直接进入正题(上一章没有看到的小伙伴可以点我主页查看)编译服务端:在D盘新建一个文件夹...

附1-Conda部署安装及基本使用(conda安装教程)

Windows环境安装安装介质下载下载地址:https://www.anaconda.com/products/individual安装Anaconda安装时,选择自定义安装,选择自定义安装路径:配置...

如何配置全世界最小的 MySQL 服务器

配置全世界最小的MySQL服务器——如何在一块IntelEdison为控制板上安装一个MySQL服务器。介绍在我最近的一篇博文中,物联网,消息以及MySQL,我展示了如果Partic...

如何使用Github Action来自动化编译PolarDB-PG数据库

随着PolarDB在国产数据库领域荣膺桂冠并持续获得广泛认可,越来越多的学生和技术爱好者开始关注并涉足这款由阿里巴巴集团倾力打造且性能卓越的关系型云原生数据库。有很多同学想要上手尝试,却卡在了编译数据...

面向NDK开发者的Android 7.0变更(ndk android.mk)

订阅Google官方微信公众号:谷歌开发者。与谷歌一起创造未来!受Android平台其他改进的影响,为了方便加载本机代码,AndroidM和N中的动态链接器对编写整洁且跨平台兼容的本机...

信创改造--人大金仓(Kingbase)数据库安装、备份恢复的问题纪要

问题一:在安装KingbaseES时,安装用户对于安装路径需有“读”、“写”、“执行”的权限。在Linux系统中,需要以非root用户执行安装程序,且该用户要有标准的home目录,您可...

OpenSSH 安全漏洞,修补操作一手掌握

1.漏洞概述近日,国家信息安全漏洞库(CNNVD)收到关于OpenSSH安全漏洞(CNNVD-202407-017、CVE-2024-6387)情况的报送。攻击者可以利用该漏洞在无需认证的情况下,通...

Linux:lsof命令详解(linux lsof命令详解)

介绍欢迎来到这篇博客。在这篇博客中,我们将学习Unix/Linux系统上的lsof命令行工具。命令行工具是您使用CLI(命令行界面)而不是GUI(图形用户界面)运行的程序或工具。lsoflsof代表&...

幻隐说固态第一期:固态硬盘接口类别

前排声明所有信息来源于网络收集,如有错误请评论区指出更正。废话不多说,目前固态硬盘接口按速度由慢到快分有这几类:SATA、mSATA、SATAExpress、PCI-E、m.2、u.2。下面我们来...

新品轰炸 影驰SSD多款产品登Computex

分享泡泡网SSD固态硬盘频道6月6日台北电脑展作为全球第二、亚洲最大的3C/IT产业链专业展,吸引了众多IT厂商和全球各地媒体的热烈关注,全球存储新势力—影驰,也积极参与其中,为广大玩家朋友带来了...