C++ - 行者无疆 始于足下 - 行走,思考,在路上

gcc: converting to execution character set

跨平台的软件开发总会遇到各种各样的问题,尤其是在一个山寨小公司里面,更是无法避免,因为程序最开始就没有考虑跨平台的事情……

废话少说,话说前两天在编译程序的时候遇到一个诡异的问题,如题目所说,gcc报错说"cannot convert to execution character set",字面意思应该是有关字符集的问题。借助baidu,找到了freebsdchina的一个帖子,大概的解释是:

"C/C++中有这样两个编码概念:

  • source character set:源文件的编码
  • execution character set:执行环境的编码

还有一些额外的环境变量就是locale类的LC_XXX

在编译的时候,要执行从source character set到execution character set的转换。这个是由编译器来完成的。locale是在程序的运行时决定程序的行为。

由于Freebsd下gcc的局限,你要使用wide characters,就是L"xxxx"这样的,源文件的编码必须为UTF-8。如果要在shell下正确的看到std::wcout输出的文字,则你的shell的locale也必须为UTF-8。另外,如果你使用的是gnome-terminal,注意它的显示编码也必须是utf-8

如果你采用了UTF-8的方式保存文件,"xxx" 这样的字符串在你的程序中就是以utf-8的方式保存的, L"xxx"这样的字符串在你的程序中就是以UCS-4的方式保存的。

为了永远摆脱乱码的困扰,我建议大家忍一忍,都使用UTF-8的方式保存文件吧。"

我忽然想起来,在我的iOS工程中,所有的源代码文件都是从原先的Windows Mobile工程引入的,自然所有的源文件编码都是GBK系列的(至于GBK、GB2312以及GB18030之间有哪些微妙的关系,我也懒得搞清楚了),而VMware里面的Mac系统编码是Unicode,所以可能会出现这个问题。不过为了确认,我又回到自己的Linux系统下,在UTF-8的终端下编译了一个GBK编码的c语言文件,结果也可以顺利执行,看来这可能与gcc的版本有关。我的ArchLinux下的gcc是4.6版本,而Mac OS X下的gcc似乎还停留在3.x版本。

至于字符编码的转换,在Linux下的基础工具和函数库都是iconv,但是iconv比较原始,无法自动检测文件的编码,需要显式指定,enca基于iconv,但是提供了根据语言自动检测文件编码的功能,并且更加健壮,更适合于脚本批处理。我用下面的命令将所有的源文件编码转换成UTF-8:

find . -name \*.c -o -name \*.h -o -name \*.cpp | xargs enca -L zh_CN -x UTF-8

至于Unicode,UTF-8,GB18030,ISO-8859,ASCII等,除了ASCII,我一个都没有彻底搞明白。罢了罢了,还是期待Unicode早点“一统江湖,千秋万载”吧。

gcc main.h: Precompiled header

c语言的开发总会遇到各种各样的障碍,诸如字节顺序、换行符、文件编码,以及复杂的工程构建问题,等等;而时不时冒出来的似曾熟悉的新概念,也会让你由衷的感叹:"C语言,简约而不简单"。

给定两个文件main.h以及main.c:

#include <stdio.h>
#include "main.h"

int main(int argc, const char *argv[])
{
    printf("Hello, World\n");
    return 0;
}

地球人都知道,可以用gcc main.c来得到可执行文件:a.out。但是,如果是gcc main.h呢?

lox@lox-pad ~/tmp/precompiled_header % gcc main.c
lox@lox-pad ~/tmp/precompiled_header % time gcc main.h
gcc main.h  0.03s user 0.01s system 89% cpu 0.045 total
lox@lox-pad ~/tmp/precompiled_header % ls -lh
total 1.7M
-rwxrwxr-x 1 lox users 6.6K May 11 20:02 a.out*
-rw-rw-r-- 1 lox users  106 May 11 19:53 main.c
-rw-rw-r-- 1 lox users   19 May 11 19:53 main.h
-rw-rw-r-- 1 lox users 1.7M May 11 20:03 main.h.gch

我们得到了一个新的文件,main.h.gch,并且,这个文件竟然有1.7M这么大。这个文件,就是我们要谈到的预编译头文件(Precompiled header)。

这个预编译头文件究竟有何作用?为何我们要多此一举?这要从编译器的具体工作原理讲起。大体来讲,编译器的主要工作过程为:预处理-->词法分析-->语法分析-->语义分析-->代码生成与优化这几个过程。而对c语言来说,预处理的过程主要是处理源代码文件中的以"#"开始的预编译指令,诸如"#include","#define"等,这个过程包括(参考《程序员的自我修养》P39):

  • 删除"#define",展开所有的宏定义
  • 处理所有的条件编译指令
  • 处理"#include"指令,将被包含的文件插入到该预编译指令的位置(这个过程是递归的)
  • 删除所有的注释
  • 添加行号和文件名标识,用于调试信息
  • 保留所有的#pragma编译器指令

可以看出,对于巨型的头文件,诸如Windows系统的Windows.h和Mac系统的Cocoa.h,这个预处理的过程也是相当的耗费编译时间的,而这类的头文件都有一个特点,就是头文件的内容基本不会改变,但是每次编译都要重新分析这些头文件,无疑是一种巨大的浪费,所以gcc main.h做的工作就是,将main.h的预处理结果存储位main.h.gch文件,以后再用gcc main.c编译的时候,gcc会先搜索main.h.gch这样的预编译头文件,如果预编译头文件存在,那么gcc就省去了重新分析main.h的过程。对于大型的工程来讲,这样的策略带来的编译时间上的减少,还是相当可观的。

Visual Studio某些工程中常见的stdafx.h,以及Xcode中大部分工程默认带有的.pch文件,都是预编译头文件。

与预编译头文件相关的还有一个Prefix header的概念,我将它翻译成"预包含头文件",这种Prefix header不但会默认被预编译,而且会默认被每个源文件包含,目前我所知道的,只有Xcode才有这么邪恶的做法。

事实上我不太喜欢这种Xcode这种出于好意的做法,当然并不是所有的开发者都需要了解这么多,多数人只需要按照模版新建工程然后用Interface Builder拖拖拽拽就好,至于背后生成了哪些肮脏的代码,who cares。

真正了不起的程序员对自己的程序的每一个字节都了如指掌。

所以我还是喜欢开个终端,架上vim和emacs,搭好makefile,all from scratch。谈到这里,我们再稍微挖掘一下gcc的潜力:gcc -H main.h:

lox@lox-pad ~/tmp/precompiled_header % gcc -H main.h
. /usr/include/stdio.h
.. /usr/include/features.h
... /usr/include/sys/cdefs.h
.... /usr/include/bits/wordsize.h
... /usr/include/gnu/stubs.h
.... /usr/include/bits/wordsize.h
.... /usr/include/gnu/stubs-64.h
.. /usr/lib/gcc/x86_64-unknown-linux-gnu/4.6.0/include/stddef.h
.. /usr/include/bits/types.h
... /usr/include/bits/wordsize.h
... /usr/include/bits/typesizes.h
.. /usr/include/libio.h
... /usr/include/_G_config.h
.... /usr/lib/gcc/x86_64-unknown-linux-gnu/4.6.0/include/stddef.h
.... /usr/include/wchar.h
... /usr/lib/gcc/x86_64-unknown-linux-gnu/4.6.0/include/stdarg.h
.. /usr/include/bits/stdio_lim.h
.. /usr/include/bits/sys_errlist.h
Multiple include guards may be useful for:
/usr/include/bits/stdio_lim.h
/usr/include/bits/sys_errlist.h
/usr/include/bits/typesizes.h
/usr/include/gnu/stubs-64.h
/usr/include/gnu/stubs.h
/usr/include/wchar.h
lox@lox-pad ~/tmp/precompiled_header %   

看到了吧,gcc -H给出了一个头文件中递归包含的所有头文件。

事实上最近我在做毕业设计,一套Window Mobile代码向iOS的移植,常常会看到源代码中包含一些无用头文件,虽有使用了include guard,但是某些时候还是会引起定义冲突,比如我就碰到了源代码中typedef int Boolean和MacTypes.h中typedef unsigned char Boolean之间的冲突。我在想有没有这样一款工具,可以扫描整个工程中无用的头文件以及所有源代码中无用的#include,然后做自动的清理工作呢?gcc -H应该可以作为一个开端。




Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee