Linux江湖06:感悟GNU C以及将Vim打造成C/C++的半自动化IDE

  C语言在Linux系统中的重要性自然是无与伦比、不可替代,所以我写Linux江湖系列不可能不提C语言。C语言是我的启蒙语言,感谢C语言带领我进入了程序世界。虽然现在不靠它吃饭,但是仍免不了经常和它打交道,特别是在Linux系统下。

  Linux系统中普遍使用的是GNU-C,这里有一份Gnu-C语言手册.pdf。The GNU C Reference Manual的主页在这里:http://www.gnu.org/software/gnu-c-manual/。C语言的内核极其紧凑,该手册总共只有91页,去掉目录、附录和索引后就只有70页。我一般一个多小时就可以将其从头至尾复习一遍。我曾有过将其翻译成中文的想法,后来还是放弃了。翻译这种字斟句酌的事情还是让别人来干吧。我只写写我自己的感悟。

感悟一:C语言标准干不过GNU扩展

  最近为了研究X Window的底层协议,开始尝试使用XCB编程。当我打开XCB的头文件的时候,我被大量的__restrict__关键字惊呆了,好在有GNU C语言手册为我答疑解惑。__restrict__又是一个GNU扩展的关键字,后面我会详细讲解该关键字的用途。其实C语言的C99标准中已经引入了restrict关键字,没有前后的下划线,但是在大量的开源代码中,使用最普遍的还是GNU的扩展,而不是C语言标准。

  和restrict关键字有相同命运的还有inline、_Complex等,它们都是在C99标准中引入的关键字,但是其实在C99标准出来之前,GNU C中早就有了__inline__、__complex__等扩展关键字。还记得多年前我学习Linux 0.11版的源代码时,看到大量的__inline__曾经疑惑不已,不知道为什么Linus在91年就能用上了如此先进的语言功能,后来才知道,这是GNU的扩展关键字。

  C语言的标准有C89和C99,使用GCC的时候甚至要显示指定-std=c99才能全面支持C99标准,所以在开源界,大家还是喜欢首选GNU的扩展关键字。比如__inline__、__complex__和__restrict__。总而言之,C语言标准干不过GNU扩展。

  下面来看看__restrict__的真正含义。还记得CSDN上曾经载过一篇文章《为什么有些语言会比别的快》,其中提到“很长一段时间,相同的两个程序在Fortran和C(或者C++)中运行,Fortran会快一些,因为Fortran的优化做的更好。这是真的,就算C语言和Fortran的编译器用了相同的代码生成器也是一样。这个不同不是因为Fortran的某种特性,事实上恰恰相反,是因为Fortran不具备的特性。”这是因为C语言中的指针给编译器的优化带来了困难,文章中继续说道:“问题就来了。这些指针可以代替任何内存地址。更重要的是,他们可以重叠。输出数组的内存地址也可以同时是输入数组的。甚至可以部分重叠,输出数组可以覆盖一个输入数组的一半。这对编译器优化来说是个大问题,因为之前基于数组的优化不再适用。特别的,添加元素的顺序也成问题,如果输出重叠的数组,计算的结果会变得不确定,取决于输出元素的动作是发生在元素被覆盖之前还是之后。”

  有了__restrict__,C语言的该问题将不复存在。用__restrict__修饰一个指针后,①该指针只能在定义的时候被初始化;②不会再有别的指针指向该指针指向的内存,因此编译器可以对算法进行优化。如下代码:

int * __restrict__ p = (int*)malloc(100*sizeof(int));

指针p有__restrict__关键字修饰,所以它只能在定义的时候被初始化,以后不能赋值,而没有__restrict__修饰的指针,可以随时赋值,如下:

int arr[100];
int* pArr;
pArr = arr;

指针pArr没有被__restrict__关键字修饰,所以可以将数组的首地址赋值给它。

  比如我们定义一个函数对两块数据进行操作,结果放入第3块内存,如下:

void func1(void* p1, void* p2, void* p3, int size){
    for(int i=0; i<size; i++){
        p3[i] = p1[i] + p2[i];
    }
}

很显然,由于编译器没办法判断指针p1、p2、p3指向的内存是否重叠,所以无法进行优化,加上__restrict__关键字后,如下:

void func1(void* __restrict__ p1, void* __restrict__ p2, void* __restrict__ p3, int size){
    for(int i=0; i<size; i++){
        p3[i] = p1[i] + p2[i];
    }
}

相当于明确告诉编译器这几块内存不会重叠,所以编译器就可以放心大胆对程序进行优化。

  另一个关键字是_Complex,C99才引入,而且需要包含<complex.h>头文件。其实在GNU C中,早就有__complex__、__real__、__imag__等扩展关键字。如下代码:

 1 #include <stdlib.h>
 2 #include <stdio.h>
 3
 4 int main(){
 5     __complex__ a = 3 + 4i;
 6     __complex__ b = 5 + 6i;
 7     __complex__ c = a + b;
 8     __complex__ d = a * b;
 9     __complex__ e = a / b;
10     printf("a + b = %f + %fi\n", __real__ c, __imag__ c);
11     printf("a * b = %f + %fi\n", __real__ d, __imag__ d);
12     printf("a / b = %f + %fi\n", __real__ e, __imag__ e);
13     return 0;
14 }

  可以看到,在C语言中也可以直接对复数进行计算。数值计算再也不是Fortran的专利。

感悟二:指针和数组还真是不一样

  从学C语言开始,老师就教导我们说指针和数组是一样的,它们可以用同样的方式进行操作。而事实上,指针和数组还是有差别的。

  指针不保存数据的长度信息,而数组有,如下代码:

 1 #include <stdlib.h>
 2 #include <stdio.h>
 3
 4 int main(){
 5     int* p = (int*)malloc(100*sizeof(int));
 6     int arr[100] = {0};
 7     printf("The size of p: %d\n", sizeof(p));
 8     printf("The size of arr: %d\n", sizeof(arr));
 9     return 0;
10 }

这段代码的运行结果为:

The size of p: 8
The size of arr: 400

  我们经常可以使用如下的代码片段来获得一个数组中有多少个元素,如下:

int arr[100];
size_t length = sizeof(arr)/sizeof(int);

  但是,当使用数组作为函数的参数的时候,数组会退化成指针。如下代码:

 1 #include <stdlib.h>
 2 #include <stdio.h>
 3
 4 void test_array(int arr[]){
 5     printf("The size of arr in function: %d\n", sizeof(arr));
 6     return;
 7 }
 8
 9 int main(){
10     int arr[100] = {0};
11     printf("The size of arr in main: %d\n", sizeof(arr));
12     test_array(arr);
13     return 0;
14 }

这段代码的运行结果为:

The size of arr in main: 400
The size of arr in function: 8

感悟三:C语言中的不完全类型(Incomplete Types)

  在GNU C中可以定义不完全类型,不完全类型主要有两种,一种是空的结构,一种是空的数组,比如:

struct point;
char name[0];

空的结构不能定义变量,只能使用空结构的指针。空结构可以在后面再将它补充完整,如下:

struct point{
    int x,y;
};

空结构在定义链表的时候经常用到,如下:

struct linked_list{
    struct linked_list* next;
    int x;
    /*other elements here perhaps */
}
struct linked_list* head;

  还有一种不完全类型就是将一个结构的最后一项定义为一个空的数组,这样可以用来表示一个可变长度的结构或数组,演示该技术的代码如下:

 1 #include <stdlib.h>
 2 #include <stdio.h>
 3
 4 typedef struct {
 5     int length;
 6     int arr[0];
 7 } incomplete_type;
 8
 9 int main(){
10     char hello[] = "Hello, world!";
11     int length = sizeof(hello) / sizeof(char);
12     incomplete_type* p = (incomplete_type*)malloc(sizeof(int) + length*sizeof(char));
13     p->length = length;
14     for(int i=0; i<p->length; i++){
15         p->arr[i] = hello[i];
16     }
17     printf("p->length=%d\n", p->length);
18     printf("p->arr=%s\n", p->arr);
19 }

打造C/C++的IDE

  后面的内容展示如何将Vim打造成一个半自动的C/C++ IDE。读过我的Java博客的朋友应该知道,其实我更喜欢用Eclipse。只有在需要写非常简单的程序(比如做习题)的情况下,我才会用Vim。这在我的《打造属于自己的Vim》中有论述。在这篇文章中我展示了怎么使用Vundle管理插件以及怎么怎么阅读帮助文档,同时展示了taglist.vim的简单用法。如果要用Vim来写C/C++程序,还需要做少许扩展。

  第一,安装以下几个插件,由于使用Vundle管理插件,所以只需要把插件名写入.vimrc配置文件,然后运行:BundleInstall即可,如下图:

  分别介绍一下这几个插件。The-NERD-tree是一个浏览目录和文件的插件,可以使用:help NERD_tree.txt查看它的帮助文档。taglist.vim是浏览符号以及在符号之间跳转的插件,使用:help taglist.txt查看它的帮助文档。a.vim是在源代码文件和头文件之间跳转的插件,不需要帮助文档,它的命令就是:A。c.vim是提供IDE功能的主要插件,它提供的功能有自动注释、反注释、自动插入代码块及自动运行,如果安装了splint,还可以对代码进行静态检查,使用:help csupport.txt查看它的文档。OmniCppComplete是一个提供自动补全功能的插件,使用:help omnicppcomplete.txt查看它的文档。

  这些插件中,taglist.vim和OmniCppComplete需要ctags软件的支持,所以需要安装exuberant-ctags软件包,在Fedora 20中,只需要使用yum install ctags即可自动安装。

  第二,生成tags数据库,并将其加入到Vim中。

  我们写C程序的时候,使用到的文件主要存在于两个地方,一个是我们工作的当前目录,另外一个是/usr/include。所以要到/usr/include目录下使用ctags命令生成tags数据库文件。为了使tags数据库中包含尽可能多的信息(结构、枚举、类、函数、宏定义等等),需要指定ctags的参数,如下:

  然后将该tags文件的路径加入到.vimrc配置文件中,同时设置一个键盘映射,使得按Ctrl+F12时,在工作目录中调用ctags命令。如下配置文件的最后两行:

  然后,在使用Vim写C程序的时候,如果输入了.、->这样的元素,则其成员会自动补全。如果输入的是一个字符串(比如函数名),可以按Ctrl-X Ctrl-O调用自动补全,如下图:

  不仅会弹出候选窗口,而且在最上面的窗口中会显示函数的完整的签名,及其所在的文件。这对于我们经常记不全函数名、记不清函数签名的人来说,已经是莫大的福音了。

  taglist.vim和OmniCppComplete插件提供的功能用起来都只需要一个命令,而c.vim提供的命令就比较多了。所以,这里有一个c-hotkeys.pdf,大家一边写程序一边参考吧。

  相比网上其它的将Vim打造成IDE的文章,我的配置比较简单,基本上只安装了几个插件,而没有做过多的设置。当我需要某个功能的时候,我会使用命令显式地调用它,所以,称它为半自动化IDE吧。

(京山游侠于2014-06-29发布于博客园,转载请注明出处。)

Linux江湖06:感悟GNU C以及将Vim打造成C/C++的半自动化IDE

时间: 2024-10-12 17:12:39

Linux江湖06:感悟GNU C以及将Vim打造成C/C++的半自动化IDE的相关文章

Linux江湖02:打造属于自己的Vim

Linux系统中很多东西都是以脚本代码.配置文件的形式存在,使用Linux系统时,需经常对这些文件进行编辑.很显然,如果没有文本编辑器,江湖之路寸步难行. 我的选择是Vim.Vim是Linux系统上的最著名的文本/代码编辑器,也是早年的Vi编辑器的加强版,被誉为文本/代码编辑器之中最为优秀经典的上古神器. 它之所以会获得如此美誉,我想主要有以下原因:1.它古老而神秘,学习曲线陡峭,难以驾驭,但是一旦学会则受益匪浅.如今被看作是高手.Geek们专用的编辑器.所以尽管 Vim 已经是古董级的软件,但

Linux江湖23:使用Eclipse和Gnu Autotools管理C/C++项目

在我该系列的之前的所有随笔中,都是采用 Linux 发行版自带的包管理工具(如 apt-get.yum 等)进行软件的安装和卸载,从来没有向大家展示使用源代码自行编译安装软件的方法.但是长期混迹于 Unix/Linux 世界的童鞋们都知道,从源代码自行编译安装软件并不是那么的难,一般都是这样三个步骤: configure make make install 之所以能够把源代码的构建管理得如此简单,这得益于 Gnu 的 Autotools 工具链.在上面的三个命令中,configure 是一个脚本

Linux江湖:Just for 折腾

我在博客园开博已经有七年又十个月了.不幸的是,我的博客里一篇东西都没有.我七年又十个月前选择博客园的时候,博客园还不是像现在这样的大杂烩.那时,博客园的内容主要是.net方向的,同时,博客园还有两个分站,一个是www.cppblog.com,一个是www.blogjava.net,分别让用户发布C++和Java方面的文章和随笔.现在,www.cnblogs.com这个主站俨然有一统江湖之势,我在另外两个分站上的博客访客寥寥.So,我又把这个闲置了快八年的博客启用了. 我是非常喜欢博客园的,这么多

Linux江湖01:玩转Linux系统的方法论 (转载)

http://www.blogjava.net/youxia/archive/2015/01/08/linux001.html 2014年上半年,我是在写RCP系列.然后,由于要准备研究生毕业论文和答辩, 所以就中途停了下来.再后来,我又在博客园主站开始写Linux江湖系列.经过大半年的努力,Linux江湖系列已经有十几篇了.在这里,我将把其中的内 容整理后,转发到我的Java博客中.至于RCP系列,我还是会接着写的,不过要等我的心再次沉下来才行. Linus说“Just for fun”,而我

Linux/RedHat 编译安装GNU gcc 4.9.0 (g++)

这里说的是编译安装,yum/apt-get 等安装方法比较简单,不阐述! 1.下载源码包:gcc.gnu.org 2.解压: tar -xjvf gcc-4.9.0.tar.bz2 3.下载编译所需的依赖包: 最简单的方法,直接执行: ./contrib/download_prerequisites 脚本自动下载依赖包 gmp, mpfr,mpc. 也可以手动下载然后移到/gcc-4.9.0目录下面自动一起安装,或者自行先编译安装 4.执行configure命令,产生makefile: mkdi

Linux江湖17:适合数值计算的语言需要具备什么样的特色

2015年1月,我继续徜徉在数值计算的海洋中.这段时间里,我抽空看了Python科学计算和数值分析方面的书,也仔细研读了Octave的用户手册,甚至连古老的Fortran.新兴的R语言我都去逐一了解.对于数值计算的库,我了解了一下Boost的uBLAS,以前也用过OpenCV,当然,了解最多的还是Python中的NumPy.SciPy和pandas. 前几篇随笔搞了不少工具论,所以今天我就专门来论一论编程语言.我的这个Linux江湖系列是一会儿方法论一会儿工具论,每过一段时间也谈谈编程语言.今天

Linux江湖12:桌面美化那点事儿

各个Linux桌面发行版刚拿到手的时候,都或多或少有点不满意,对它们进行一些改造是必须的.网上不乏各种Linux桌面美化的教程和经验贴,对我们这些Linux爱好者来说都是很好的参考资料.进行桌面美化之前,请谨记以下几点: 1.桌面美化是一个仁者见仁.智者见智的事.有可能你认为很美的界面,别人认为土得掉渣:有可能你认为很炫的特效,别人认为华而不实.就拿我来说,我就不喜欢桌面小部件,不管是在电脑上还是在手机上.所以我的电脑桌面总是光秃秃,除了背景图片啥都没有.但是我喜欢将控制台窗口半透明化.所以,对

Linux江湖07:硬盘分区的陷阱及应对

之所以想到写这篇,是因为本人在折腾Linux系统的过程中,有多次掉入硬盘分区的陷阱的经历.最近几天,再一次掉入坑中,折腾了两天才从坑中爬出来.经过多方查询资料,终于弄明白了硬盘分区的一些概念.下面将其记录下来,以警示来者. 说起我自己掉坑的经历,无不与WinXP和Linux的激烈碰撞有关.多年前,我就开始在一台电脑上同时安装WinXP和Linux,只要遵守先安装WinXP再安装Linux的顺序,就不会出问题,Linux的安装程序会自动识别多系统,安装完成后可顺利启动多系统.有一天,我觉得单用Li

Linux江湖04:Linux桌面系统字体配置要略(上)

字体显示效果测试 这一段是为了测试宋体字的显示效果,包括宋体里面自带的英文字体,“This is english,how does it look like?”.这一行是小字.后面几个字是加粗的宋体.标点符号“,.::!” 这一段是为了测试黑体字的显示效果,包括黑体里面自带的英文字体,“This is english,how does it look like?”.这一行是小字.标点符号“,.::!”.微软雅黑是什么样子的呢“,.:!”? This paragraph shows how doe