为什么Mac OS上的C运行时允许预先组合和分解的UTF-8?

时间:2016-07-20 14:41:19

标签: c macos utf-8 posix

所以我们都知道Mac OS上的文件系统有使用完全分解的UTF-8的古怪功能。例如,如果您调用POSIX API(例如realpath()),那么您将从Mac OS获得这样一个完全分解的UTF-8字符串。但是,当使用像fopen()这样的API时,传递预先组合的UTF-8似乎也可以正常工作。

这是一个小型演示程序,它试图打开一个名为ä的文件。第一次调用fopen()传递一个预先组合的UTF-8字符串,第二次调用传递一个分解的UTF-8字符串,令我惊讶的是两个工作。我希望只有第二个可以工作,但预先组合的UTF-8也能正常工作。

#include <stdio.h>

int main(int argc, char *argv[])
{
    FILE *fp, *fp2;

    fp = fopen("\xc3\xa4", "rb");       // ä as precomposed UTF-8
    fp2 = fopen("\x61\xcc\x88", "rb");  // ä as decomposed UTF-8

    printf("CHECK: %p %p\n", fp, fp2);

    if(fp) fclose(fp);
    if(fp2) fclose(fp2);

    return 0;
}

现在回答我的问题:

  1. 这是定义的行为吗?即允许将预先组合的UTF-8传递给POSIX API,还是应该总是通过分解的UTF-8?

  2. fopen()等函数如何知道传递的文件是否包含预先组合或分解的UTF-8?这甚至不会导致各种各样的问题,例如:打开错误的文件,因为传递的字符串可以用两种不同的方式解释,因此可能指向两个不同的文件?这让我感到有些困惑。

  3. 修改

    为了完成混淆,这种奇怪的行为似乎甚至不限于文件I / O.看看这段代码:

    #include <stdio.h>
    
    int main(int argc, char *argv[])
    {
        printf("\xc3\xa4\n");
        printf("\x61\xcc\x88\n");
    
        return 0;
    }
    

    两个printf调用完全相同,即它们都打印字符ä,第一个调用使用预先组合的UTF-8,第二个调用使用分解的UTF-8。这真的很奇怪。

1 个答案:

答案 0 :(得分:1)

Unicode字符串中有两种不同类型的等价:一种是规范等价,另一种是兼容性。由于您的问题是关于软件似乎被认为相同的字符串,因此我们将重点放在规范等价(OTOH,兼容性允许语义差异,因此它不在主题中这个问题)。

引用维基百科中的Unicode equivalence

  

定义为规范等效的代码点序列   假设印刷时具有相同的外观和含义   显示。例如,代码点U + 006E(拉丁文小写   “n”)后跟U + 0303(组合波形符“◌”)定义为   Unicode规范地等效于单个代码点U + 00F1   (西班牙字母表中的小写字母“ñ”)。 因此,那些   序列应以相同的方式显示,应予以处理   应用程序(如按字母顺序排列的名称或搜索)的方式相同,   并且可以互相替换

换句话说,如果两个字符串规范等效,软件应该考虑两个字符串代表完全相同的东西。所以,MacOS在这里做的正确:你有两个不同的UTF-8字符串(一个分解,另一个预先组合),但它们规范等效,所以它们映射到同一个对象(同一个文件)你的例子中的名字)。这是正确的(记住“应该通过字母顺序名称或搜索等应用程序以相同的方式处理,并且可以在上面的引用中替换彼此”行。)

我真的不明白你关于printf()的第二个例子。是的,分解字符和预合成字符都呈现相同的输出。这正是Unicode支持的字符的双重表示中的要点:您可以选择是否使用预先组合的字节序列或分解的字节序列来表示组合字符。它们打印相同的视觉效果,但它们的表现形式不同。如果两个表示都规范等效(在某些情况下它们不是),那么系统必须将它们视为同一对象的两个表示。

为了在您的软件中更舒适地管理所有这些,您应该normalize your Unicode strings才能使用它们。