argv的编码是什么?

Max*_*oke 38 c linux unicode encoding

我不清楚C中使用了什么编码argv.特别是,我对以下场景感兴趣:

  • 用户使用区域设置L1创建名称N包含非ASCII字符的文件
  • 稍后,用户使用区域设置L2在命令行上选项卡填写该文件的名称,该文件作为命令行参数提供给程序P.

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

我观察到在Linux上,在UTF-8语言环境中创建一个文件名然后在(例如)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.

当用户实际按Enter键以使用该参数运行P时,该名称N2将转换回Unicode,N1再次产生.这N1是现在UCS2格式可供程序通过GetCommandLineW/ wmain/ tmain,但用户GetCommandLine/ main会看到的名称N2在当前区域设置(代码页).

OS X.

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

使用Unicode终端,我认为终端会在Unicode缓冲区中构建命令行.因此,当选项卡完成时,它会将文件名作为Unicode文件名复制到该缓冲区.

当您运行该命令时,该Unicode缓冲区将转换为当前区域设置L2,并通过该方式提供给程序argv,程序可以将具有当前区域设置的argv解码为Unicode以供显示.

Linux的

在Linux上,一切都是不同的,我对正在发生的事情感到困惑.Linux将文件名存储为字节字符串,而不是Unicode.因此,如果您N在区域设置L1中创建一个名称为file N的字节字符串,则存储在磁盘上.

当我稍后运行终端并尝试选项卡完成名称时,我不确定会发生什么.在我看来,命令行被构造为字节缓冲区,并且作为字节字符串的文件名称只是连接到该缓冲区.我假设当你键入一个标准字符时,它会被动态编码为附加到该缓冲区的字节.

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

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

附加功能

这是一个简单的候选程序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;
}
Run Code Online (Sandbox Code Playgroud)

您可以使用locale -a查看可用的区域设置,并用于export LC_ALL=my_encoding更改区域设置.

Max*_*oke 20

感谢大家的回应.我已经从这个问题中学到了很多东西,并且发现了以下解决了我的问题:

  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代码点.当该代码点被解码回字节流时,它再次成为原始字节.这允许从当前编码中无效的argv数据(即,以当前语言环境以外的名称命名的文件名)通过本机Python字符串类型往返,并返回到字节而不会丢失信息.

如你所见,情况非常混乱:-)


Phi*_*ipp 6

我现在只能谈论Windows.在Windows上,代码页仅适用于遗留应用程序,不供系统或现代应用程序使用.Windows使用UTF-16(并且已经使用了很长时间)用于所有内容:文本显示,文件名,终端,系统API.UTF-16和遗留代码页之间的转换仅在最高级别执行,直接在系统和应用程序之间的接口上执行(从技术上讲,旧的API函数实现了两次 - 一个功能FunctionW可以完成实际工作并期望UTF -16个字符串,以及一个FunctionA简单地将输入字符串从当前(线程)代码页转换为UTF-16的兼容性函数调用FunctionW,并转换回结果).Tab-completion应该总是产生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数组.