之前的学习一直放在有道云笔记上,这次决定把笔记迁移到csdn上,方便相互学习。
学习了这么长时间的linux c/c++的开发,觉得这个方向就是个无底洞,对一些问题或特性的理解不能停留在语言的语法层面,还需要深入到语言规范,操作系统(Linux)的平台特性,甚至是硬件设计上去。为什么会这样说,举一些例子:
为什么说要了解语言的设计规范:我们可以定义一个空的结构体,对空结构sizeof,计算出来的大小是1而不是0:
struct A{};//空结构体
sizeof(struct A); //结果为1,而不是0
原因在于struct结构体不仅仅是一个c++实体,还是一个对象,而对象的本质是一块带有某些性质的存储区域,某些性质包括大小,对齐要求,存储期,生命周期,值,名字等。因此,不管对象是否为空都至少需要分配一个字节的空间;对比一下,函数虽然是一个c++实体,但不是对象,因此sizeof一个函数调用时,计算的是函数返回值得大小:
int foo();//函数的声明,此处没有定义函数
sizeof(foo());//计算出来的值为4,即int的大小(32bit平台情况)。
注意:sizeof是一个“不求值表达式”,它只是在编译时期对函数的返回值进行计算,并不会真正的调用函数(由于采用分离编译,该函数定义可能放在别的翻译单元中,需要等连接器ld进行符号解析之后才能关联起来,调用的话还需要在运行时分配栈帧,所以不可能调用);另外foo后面要加上括号,因为在编译器眼中foo只是一个名字,如果不加括号,编译器在“名字查找”时会取找找看有没有声明一个foo的变量,如果没有就会报“找不到名字”的编译时错误,加上括号后编译器才知道这个是函数。
为什么说要了解平台特性:我们c语言标准库libc中得printf()函数会向终端打印字符,表面上看是这样,但实际上printf()函数是向底层的文件描述符STDOUT_FILENO打印的,而该文件描述符默认定向到/dev/tty上,即终端上,如果事先用dup,dup2等函数来重定向文件描述符STDOUT_FILENO某个文件上,那么再调用printf()函数就不会再终端上输出了。看代码:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main(){
printf("hello\n"); //会向终端打印
int fd = open("./temp.txt",O_RDWR); //打开一个已经存在的文件
close(STDOUT_FILENO); //先关闭文件描述符
dup2(fd,STDOUT_FILENO); //把STDOUT_FILENO重定向到fd上
printf("world\n"); //再次打印,字符串就在文件中了
close(fd); //关闭打开的文件
return 0;
}
为什么说要了解硬件的特性:我们知道可执行文件的数据是经过“内存对齐”过的,使得地址值为数据大小的整数倍,如地址0xfffff4上放置的数据只能是char,short,int,而不能是double,因为硬件产生的地址是对齐过的,如果数据不对齐,会导致可能需要2条指令才能取到数据,这样效率就低了。
总结下,linux c/c++ 不是一蹴而就的,需要一层一层的深入,对问题的理解不能片面,需要从多个角度去看待;另外,我觉得我们的linux c/c++ 和其他语言(java)最大区别在于,前者是一个像是“规范”,而后者是一个“产品”,例如java语言,它的语法,编译器,虚拟机等都是由oracle公司来维护的,虽然说“一次编译,到处运行”,但也只是编译后的字节码能跨平台,虚拟机不能跨平台且不开源,而我们的c/c++的编译器有很多不同的实现(GNU/GCC,LLVM/CLANG),而且在liunx上生成elf格式可执行文件,可以直接运行不需要虚拟机。
平时在学习时参看的一些书籍和网站分享下:
书籍:c++ primer,c++ primer plus,深度探索c++对象模型,csapp,apue,linux的man page
网站:c++参考手册,它能使你从一个更高的层面俯瞰c++;c++之父的主页 。