一个关于Vim扩展TAB键的问题

今天遇到一个奇怪的问题:明明我在.vimrc中开启了expandtab选项,但是当我编辑Makefile文件时,敲入的TAB就是无法被VIM自动转换为四个空格(已经设置tabstop=4,shiftwidth=4),通过":set expandtab?"查看该选项值也居然是"noexpandtab";编辑其他文件(如.c、.h文件甚至是无扩展名的文件)时expandtab却都是开启的,TAB也可被自动转换,百思不得其解!

最初怀疑是compatible的设置对expandtab产生了影响。打开我的.vimrc,发现我设置的是“set nocompatible”,“compatible”已经被关掉,不会对expandtab产生影响。又想了想,假设受影响,那么所有文件都应该受到影响才对,不应该只有Makefile这类文件受影响。

想到这里,突然开了窍!是不是我开启的文件类型检测导致的呢?我在.vimrc设置了"filetype plugin on"。又看了一下这个设置的相关Manual,虽然没有直接给出答案,但是顺藤摸瓜,我也找到了原因。

因为开启了文件类型检测,Vim在打开或新建一个文件时会自动判断文件的扩展名以确定文件类型,在$VIMRUNTIME/filetype.vim中搜索"Makefile",可看到如下脚本语句:
" Makefile
au BufNewFile,BufRead *[mM]akefile,*.mk,*.mak,*.dsp setf make

Vim将Makefile划归为"make"类型(setf make)。在$VIMRUNTIME/ftplugin下有一堆xxx.vim文件,我们从中可以找到make.vim,这个文件就是VIM针对make类型文件的设置,在打开或新建make类型文件时被VIM自动加载。

这个make.vim文件中有一行设置如下:
" Make sure a hard TAB is used, required for most make programs
setlocal noexpandtab softtabstop=0

见文知义!果不其然,就是这个问题。又试验了一下,将.vimrc中的“filetype plugin on”注释掉,再打开Makefile文件,TAB就可以被自动转换为四个空格了。

回头一想,VIM针对make类型文件设置了noexpandtab也不无道理,编写过Makefile的朋友都知道,Makefile的基本组成结构就是:
target … : prerequisites …
    command
    …
    …
其中Makefile语法要求command前面必须放置一个TAB!否则解析失败!

这回真相大白了^_^

视警告为错误

每当你Build Project代码的时候,如果看到的是满屏的Warning,那么提醒你小心了,不妨看看《高效程序员的45个习惯》中对Warning的态度和处理方式。该书中的第34个习惯讲的是“警告就是错误”! 当然这个“习惯”所阐述的内容并不是这本书首创,在很多经典的传授编程之道的书中也都提到过。

将警告作为错误来处理,说起来容易,可作起来可并不那么简单。这不仅仅只是一个态度的问题,有时候还需要有技术手段或技巧去帮助你完成。《高效程序员的45个习惯》一书的作者也在该习惯所对应的“平衡的艺术”中提到:“如果确实没有应对之策的话,那就不要再浪费更多时间了。但类似这种情况很少发生”。另外他建议应该经常使用编译器的directive(指示器),将一些实在无法避免但又确定不是潜在缺陷的警告进行提示,告知编译器这个地方的警告可以不去理会。

将警告视为错误首先需要的就是勇气,大胆的在你的Makefile中将-Werror赋予给Gcc吧。Gcc则为你提供了一定的技术手段来帮你处理面对无法避免的警告时“左右为难”的情况。

Gcc为我们提供的技术手段就是Pragmas,虽然Gcc手册中建议我们一般情况下不要显式的使用Pragmas,但必要时还是需要这个工具的帮助的。

#pragma directives这个指示器嵌入在源码中,用于在源码编译时给GCC编译器提供一些指示信息。Pragmas有多种分类,这里我们需要的是Diagnostic Pragmas。简单的说,嵌入在源码中的Diagnostic Pragmas给我们提供了如下一些能力:
-> 将某类编译警告按编译错误处理 (如:#pragma GCC diagnostic error "-Wformat")
-> 将某类编译错误按编译警告处理 (如:#pragma GCC diagnostic warning "-Wformat")
-> 忽略某类编译警告 (如:#pragma GCC diagnostic ignored "-Wformat")

Gcc对Diagnostic Pragmas的支持是随着版本进化而逐渐增强的,在gcc 3.4.6版本下Diagnostic Pragmas是不被accepted的,Gcc只accept五类Pragmas;到了gcc 4.4.4版本,Gcc已经可以支持12类Pragmas,这其中就包括Diagnostic Pragmas

通过实际的测试也可以证实以上说明。

e.g.
/* testpragma.c , gcc -Wall testpragma.c */
#include

#pragma GCC diagnostic error "-Wformat"
int main() {
    printf("%d\n", "Diagnostic Pragmas Test");
    return 0;
}

在Sparc Solaris 10上使用gcc 3.4.6编译结果如下:
gcc -Wall testpragma.c
testpragma.c:3: warning: ignoring #pragma GCC diagnostic
testpragma.c: In function `main':
testpragma.c:5: warning: int format, pointer arg (arg 2)
编译器提示忽略了diagnostic pragma指示。

而在Ubuntu 10.04 Gcc 4.4.3版本下编译结果如下:
gcc -Wall testpragma.c
testpragma.c: In function ‘main’:
testpragma.c:5: error: format ‘%d’ expects type ‘int’, but argument 2 has type ‘char *’
编译器正确执行了我们给出的指示^_^。

#pragma directive的作用范围也很好理解:
首先肯定是在同一编译单元范围内有效,在A编译单元中设置的#pragma directive,在B编译单元是无效的。比如我另外编写一个utils.c,其内容如下:
#include

void foo() {
    printf("%d\n", "In another compile unit");
}
我们将utils.c与testpragma.c一起编译,gcc -Wall testpragma.c utils.c,得到的结果是:
testpragma.c: In function ‘main’:
testpragma.c:5: error: format ‘%d’ expects type ‘int’, but argument 2 has type ‘char *’
utils.c: In function ‘foo’:
utils.c:4: warning: format ‘%d’ expects type ‘int’, but argument 2 has type ‘char *’
在testpragma.c这个编译单元,#pragma directive生效,但是在utils.c这个编译单元并不生效,依旧被诊断为Warning。

其次,在同一个编译单元中,#pragma directive影响的范围是其所在行之后的代码,直到下一次修改针对同样warning option的#pragma被放置。还是举例说明,我们改造一下testpragma.c:
/* testpragma.c */
#include

#pragma GCC diagnostic error "-Wformat"
void foo() {
    printf("%d\n", "Diagnostic Pragmas Scope Test");
}

int main() {
    printf("%d\n", "Diagnostic Pragmas Test");
    return 0;
}
编译命令执行后,Gcc给出的输出结果是:
testpragma.c: In function ‘foo’:
testpragma.c:5: error: format ‘%d’ expects type ‘int’, but argument 2 has type ‘char *’
testpragma.c: In function ‘main’:
testpragma.c:9: error: format ‘%d’ expects type ‘int’, but argument 2 has type ‘char *’
#pragma directive从头置尾一直发挥作用,两个format Warning都被当作Error报告了。

现在我们对main中的问题放松要求,将源码变为:
/* testpragma.c */
#include

#pragma GCC diagnostic error "-Wformat"
void foo() {
    printf("%d\n", "Diagnostic Pragmas Scope Test");
}

#pragma GCC diagnostic ignored "-Wformat"
int main() {
    printf("%d\n", "Diagnostic Pragmas Test");
    return 0;
}
执行编译命令后,Gcc的提示变为:
testpragma.c: In function ‘foo’:
testpragma.c:5: error: format ‘%d’ expects type ‘int’, but argument 2 has type ‘char *’
显然对-Wformat按照Error处理的作用范围仅限于foo这个函数,因为在foo之后我们修改了对-Wformat的指示!

有了编译器的支持,我们将更有信心去养成“视警告为错误”的习惯了。实际工作中,#pragma directive应用应该不多,因为多数情况下的警告都是可以通过正常的代码完善消除掉的。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats