近来换了住处,也给自己这些年买的书换了书架。好在有车,也好在依然是同城的落脚点,倒不像前几年那般费劲安排邮寄,觉得倥偬落魄。
只是将书籍整理上新书架时,看到那些未开封页的书,难免会有些愧疚。好似意味着我又虚度了几年。
以前看到过一段有些讽刺的话。大概说一个人在图书馆里,面对着许多书籍,什么也不必做,只是坐在那里,就觉得自己已经吸取了知识。时间太久,已经记不起是哪个大家说的了。何曾想过,这个讽刺竟然很适用于曾经买书时的我。上一次买的书还没看完,就趁着活动又兴致勃勃地加购了一批书。拿到书的那一刻,就好像已经得到了满足,看书就成以后该做的事了。
最近的一次买书,是在2021年。那时决定,身边的书没看完,断不能再加新书。虽然是借口,但是这两年发生了很多事情。情路受挫,至亲相继离开人世。所以到现在,我没再买过一本书。
处在人生的半路,已经没有多少勇气去回看来路。一个人生活,朝前路望去,满眼只有一个终点。以后估计也不会有新人闯入我的生活,想必能挤出很多的空闲。余生该如何安排,是个必须谨慎对待的问题。
工作之外,我没有多少社交,平时也只是宅在屋子里。看新闻,刷视频,怼网友等,基本就是最近的状态。我大概还是不甘如此浑浑噩噩下去,也为母亲晚年能够安定下来,且为自己往后生存计,宜尽早改变现今的状态,为未来规划出一个草案。
其实留给我的时间并没有多少。接下来的几年,或许是我在上海的最后一段岁月。故乡的田地已经荒芜,老家的房子经风雨损坏也来不及修缮。
一个人独来独往惯了,未曾遇到过高人指点。我只能依靠自己这并不聪明的头脑寻找一条路。种田和读书,加上这几年打工攒下的积蓄,大概能解决自己的温饱和精神所需。如果能够依靠文字给生活增加一些助益,从他人的思想里获取新的感悟,那也将是我的希冀和慰藉。
所以,现在开始认真对待读书,也是为以后的生存状态铺好基石。
孩童时期,因为贪玩毁了父亲为数不多的存书。父亲虽然是个农民,但也曾告诫我读书人应该尊重书籍。虽然谨记父亲教诲,但这世上书籍浩如烟海,往年我从中选的微末,也并不是都值得反复咀嚼的。后面两三年,在读完书架上书籍的同时,也会对他们做一些取舍。
《老师推荐的200首诗歌》
这本《老师推荐的200首诗歌》是我身边最老的书之一了。07年高三时在新华书店买的。高中痴迷现代诗歌,偶尔也东施效颦。去山西上大学,毕业后换了几个城市,一直带在身边。虽然很少翻阅,但是也不忍丢弃。如今反而更爱古诗词,好在里面也收录了不少。向来学生和学生家长比较容易忽悠。所以这本读书受众指向性明确的书,也并没有多好。
这本《瓦尔登湖》也是那时一同购买的。还是因为喜欢现代诗,且听说海子生前的背包里有这本书。到现在还没看完,里面写了什么也记不清了。归因于曾经我总是不能静下来去想象一种隐居的生活状态。
荣格的自传是大学时在网上买的。信息时代,总是能频繁看到大学生值得阅读的书单。这本书并不在那些书单中。也许我当时搜的是《梦的解析》,而这本书出现在相似栏中。当我获悉这个人被弗洛伊德先当作衣钵传人后视为逆子时,可能引起了我隐藏的叛逆属性,所以我先买了这本书。但大学时并没有翻开过书页。16年时我终于贪便宜买了《梦的解析》,初看了一番,又激起我再度翻阅《荣格自传》的兴趣。那年来了上海,稍微认真地读了一遍后,感叹读得太迟。从南怀瑾和叔本华的文章里出来,这本书仿佛让我窥到了一个可以代替宗教的信仰。神秘和奇妙的潜意识,是另一个值得探索的宇宙。
最后一本放在此篇稍加描述的书是勒庞的《乌合之众-群体心理研究》。这本书也出现在很多推荐书单中。这本书也被我作为今后书评的起点。原因很简单——这本书很薄。
我并不是因推荐书单才入的这本书。早在十年前,刚毕业的我同时也是八零后的我,有些愤青的特性。一大帮人因网易评论聚到了一个QQ群中,每天有事没事谈论时事。群里不乏一些极端激愤的人,整日把当时流行的普世价值挂在嘴边。某天群里来了一个中年大叔。从他的言论中看出此人竟然倾向于执政方,不免有几人出来和他激烈对战。他说服不了几人,几人也没吵赢过他。大叔最后在群里推荐一本勒庞的《乌合之众》(当时他说的是庞勒),然后就此退群。我看此人冷静发言,所说也有理有据,便私下加他好友。他炒股赚了些钱(当时向我推了一个英年早逝的炒股大神的博客),又是一家建筑事务所的股东,估计对我这种愣头青没多大兴趣,我们没聊几次。
书架里已经看完且近期还会再读的书有阿德勒的《自卑与超越》与《蛤蟆先生去看心理医生》。《思考,快与慢》只读了一半(接着在读乌合之众)。我比较推荐一些小孩子的家长看下《自卑与超越》。还有一些书如《我们时代的神经症人格》、《我们内心的冲突》和逻辑学被列入近期书单中。
等我看完如上书籍并有一定的消化吸收,准备再着手心理学的那些教科书。期间也会浏览《史记》和《资治通鉴》,还有几本小说和散文集,就当做消遣读物和古文修养。
此后的重心大概还是在哲学和心理学上。
读书,大概也只是在前人开垦过的土壤上,翻看他们留下的纹理。能够拾到一些前人遗留的果实,也是幸运。但企图凭此就开拓出一片新的田地,属实痴心妄想。不过人活在世上,总要有些妄想支撑。把它作为信仰的衍生品,也未尝就是愚人之行。此番心思,愿天可怜鉴。
概述
为什么需要解读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