什么是argv的编码?

我不清楚在C的argv中使用了哪些编码。 特别是,我对以下情况感兴趣:

  • 用户使用区域设置L1来创build名称N包含非ASCII字符的文件
  • 稍后,用户使用语言环境L2在命令行上制表该文件的名称,将其作为命令行参数提供给程序P

P在命令行上看到什么字节序列?

我观察到,在Linux上,在UTF-8语言环境中创build文件名,然后在(例如) zw_TW.big5语言环境中对其进行zw_TW.big5似乎会导致我的程序P被送入UTF-8而不是Big5 。 但是,在OS X上,相同的一系列操作会导致我的程序P获取Big5编码的文件名。

以下是我认为目前为止(长时间,我可能是错的,需要纠正):

视窗

文件名以一些Unicode格式存储在磁盘上。 所以Windows取名为N ,从L1(当前代码页)转换为N的Unicode版本,我们将调用N1 ,并将N1存储在磁盘上。

我后来假设的是,当后面的选项卡完成时,名称N1被转换为区域L2(新的当前代码页)以供显示。 幸运的是,这将产生原来的名字N – 但是如果N包含在L2中不可表示的字符,这将是不正确的。 我们称之为新名字N2

当用户实际按下回车键以运行该参数时,名称N2被转换回Unicode,再次产生N1 。 这个N1现在可以通过GetCommandLineW / wmain / tmain以UCS2格式提供给程序,但GetCommandLine / main用户将在当前语言环境(代码页)中看到名字N2

OS X

就我所知,磁盘存储的故事是一样的。 OS X将文件名称存储为Unicode。

对于Unicodeterminal,我认为terminal会在Unicode缓冲区中build立命令行。 因此,当您完成选项卡时,它会将该文件名作为Unicode文件名复制到该缓冲区。

运行该命令时,该Unicode缓冲区将转换为当前语言环境L2,并通过argv传送给程序,程序可以将当前语言环境的argv解码为Unicode以显示。

Linux的

在Linux上,一切都不一样,我对发生的事情感到非常困惑。 Linux将文件名存储为字节string ,而不是Unicode。 所以,如果在locale L1中创build一个名称为N的文件, N作为字节string是存储在磁盘上的文件。

当我以后运行terminal并尝试并完成名称时,我不确定会发生什么情况。 它在我看来像命令行被构造为一个字节缓冲区,文件的名称作为一个字节string被串联到该缓冲区。 我假定当你input一个标准字符时,它会被dynamic地编码成附加到该缓冲区的字节。

当你运行一个程序时,我认为这个缓冲区是直接发送给argv 。 现在, argv有什么编码? 它看起来像在命令行中键入的任何字符,而在语言环境中L2将采用L2编码,但文件名将采用L1编码 。 所以argv包含两个编码的混合!

如果有人能让我知道这里发生了什么,我真的很喜欢它。 我现在所拥有的仅仅是猜测和猜测,并不是真的合在一起。 我真正想要的是在当前代码页(Windows)或当前的语言环境(Linux / OS X)中编码argv ,但似乎并不是这种情况。

附加function

这是一个简单的候选程序P,可以让你观察自己的编码:

 #include <stdio.h> int main(int argc, char **argv) { if (argc < 2) { printf("Not enough arguments\n"); return 1; } int len = 0; for (char *c = argv[1]; *c; c++, len++) { printf("%d ", (int)(*c)); } printf("\nLength: %d\n", len); return 0; } 

您可以使用locale -a查看可用的语言环境,并使用export LC_ALL=my_encoding更改您的语言环境。

感谢大家的回应。 我已经学到了很多关于这个问题的知识,并且发现了以下解决了我的问题的事情:

  1. 如上所述,在Windows上,argv使用当前代码页进行编码。 但是,您可以使用GetCommandLineW以UTF-16格式检索命令行。 对于支持unicode的现代Windows应用程序,建议不要使用argv,因为不推荐使用代码页。

  2. 在Unix上,argv没有固定的编码:

    a)通过tab-completion / globbing插入的文件名将在argv 逐字中出现,就像它们在磁盘上被命名的字节序列一样。 即使这些字节序列在当前语言环境中没有意义,情况也是如此。

    b)用户使用IME直接输入的输入将出现在区域编码的argv中。 (Ubuntu似乎使用LOCALE来决定如何编码IME输入,而OS X使用Terminal.app编码首选项。)

这对于像Python,Haskell或Java这样的语言来说是烦人的,它们想把命令行参数当作字符串处理。 他们需要决定如何将argv解码为内部使用的String (这些语言是UTF-16)。 但是,如果他们只是使用语言环境编码进行解码,则输入中的有效文件名可能无法解码,从而导致异常。

Python 3采用的解决方案是代理字节编码方案( http://www.python.org/dev/peps/pep-0383/ ),它将argv中任何不可解码的字节表示为特殊的Unicode代码点。 当该代码点被解码回字节流时,它就再次成为原始字节。 这允许通过本地Python字符串类型从当前编码中无效的argv(即,以不同于当前语言环境命名的文件名)往返传输数据,并返回字节而不丢失信息。

正如你所看到的,情况非常混乱:-)

我现在只能说Windows。 在Windows上,代码页仅适用于传统应用程序,不能由系统或现代应用程序使用。 Windows使用UTF-16(并已经这么做了很长时间了):文本显示,文件名,终端,系统API。 UTF-16和遗留代码页之间的转换只能在最高级别上执行,直接在系统和应用程序之间的接口上执行(从技术上说,旧的API函数是两次执行的 – 功能函数FunctionW是真正的工作和期望UTF-16字符串和一个兼容性函数FunctionA ,它将当前(线程)代码页的输入字符串转换为UTF-16,调用FunctionW ,并将结果转换回来。 制表符完成应该总是产生UTF-16字符串(当使用TrueType字体时肯定会这样做),因为控制台也只使用UTF-16。 制表符完成的UTF-16文件名被移交给应用程序。 如果现在应用程序是一个遗留应用程序(即,它使用main而不是wmain / GetCommandLineW等),那么Microsoft C运行时(可能)使用GetCommandLineA让系统转换命令行。 所以基本上我认为你对Windows的看法是正确的(只有在制表符完成时可能没有涉及到转换): argv数组总是包含当前应用程序代码页中的参数,因为信息代码页(L1)在中间UTF-16阶段, 原来的程序使用已经不可逆转地丢失了。

结论与Windows一样:避免遗留代码页; 尽可能使用UTF-16 API。 如果您必须使用main而不是wmain (例如,独立于平台),请使用GetCommandLineW而不是argv数组。

测试应用程序的输出需要进行一些修改才行,您需要十六进制代码,并且需要清除负值。 或者你不能打印像UTF-8特殊字符的东西,所以你可以阅读它们。

首先修改SW:

 #include <stdio.h> int main(int argc, char **argv) { if (argc < 2) { printf("Not enough arguments\n"); return 1; } int len = 0; for (unsigned char *c = argv[1]; *c; c++, len++) { printf("%x ", (*c)); } printf("\nLength: %d\n", len); return 0; } 

然后在我使用UTF-8的Ubuntu盒子上得到这个输出。

 $> gcc -std=c99 argc.c -o argc $> ./argc 1ü 31 c3 bc Length: 3 

在这里你可以看到,在我的情况下,ü编码2个字符,而1是一个字符。 或多或少,你所期望的UTF-8编码。

这实际上和env LANG的变化是一致的。

 $> env | grep LANG LANG=en_US.utf8 

希望这个稍微澄清一下linux的情况。

/祝你好运

是的,用户在Unix上混合语言环境时一定要小心。 显示和更改文件名的GUI文件管理器也有这个问题。 在Mac OS X上,标准的Unix编码是UTF-8。 事实上,当通过Unix接口调用HFS +文件系统时,它强制执行UTF-8文件名,因为它需要将其转换为UTF-16以存储在文件系统中。