如何正确打印__FILE__所扩展的字符串?

13

考虑这个程序:

#include <stdio.h>
int main() {
    printf("%s\n", __FILE__);
    return 0;
}

根据文件名,这个程序是工作的-或者不工作。我面临的问题是,我想以编码安全的方式打印当前文件的名称。然而,如果文件包含无法在当前代码页中表示的特殊字符,则编译器会产生警告(理所当然):

?????????.c(3) : warning C4566: character represented by universal-character-name '\u043F' cannot be represented in the current code page (1252)

我该如何解决这个问题?我想将__FILE__生成的字符串存储为UTF-16编码,以便在运行时可以将其转换为任何其他系统使用的编码格式并正确打印出来。为此,我需要了解以下几点:

  1. __FILE__生成的字符串使用的是什么编码格式?似乎至少在Windows上使用当前系统代码页(在我的情况下是Windows-1252) - 但这只是猜测。这是真的吗?
  2. 我如何在构建时将该字符串存储为UTF-8(或UTF-16)编码的表示?

我的实际用例:我有一个宏,用于跟踪当前程序执行,将当前源代码/行号信息写入文件。它看起来像这样:

struct LogFile {
    // Write message to file. The file should contain the UTF-8 encoded data!
    void writeMessage( const std::string &msg );
};

// Global function which returns a pointer to the 'active' log file.
LogFile *activeLogFile();

#define TRACE_BEACON activeLogFile()->write( __FILE__ );

如果当前源文件的文件名包含当前代码页无法表示的字符,则会出现问题。


@Roddy:我正在使用MSVC9,但我也对g++ 4.x的解决方案感兴趣。 - Frerich Raabe
这在MSVC 2015中也完全失效了。为什么微软就不能制作一个不会出错的编译器呢? - Owl
5个回答

12

你可以使用标记粘贴操作符,像这样:

#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
#define WFILE WIDEN(__FILE__)

int main() {
    wprintf("%s\n", WFILE);
    return 0;
}

这看起来非常有趣!不过,它引发了一个后续问题:宽字符字符串使用什么编码?UTF-16?还是纯的、未编码的UCS-2字符串?现在对我来说,它似乎只是“延迟”了这个问题。但是,这比我的当前代码要好得多,所以我给它加上+1。 - Frerich Raabe
不幸的是,它似乎不能按预期工作:如果文件名为俄语,则只会打印“???????”。使用“dir”列出文件时也是如此。也许__FILE__确实与文件系统编码有关,但它不遵守Windows资源管理器用于显示俄语字符的任何字段? - Frerich Raabe
只在我的机器上运行。你是否使用控制台模式程序?你是否将控制台切换到支持字形的西里尔代码页和字体?例如,使用SetConsoleCP(1251)和Consolas字体。默认的控制台编码是OEM,它没有这些字形。 - Hans Passant
我正在使用控制台程序(链接器未传递/SUBSYSTEM:WINDOWS),但实际上我是通过OutputDebugStringW打印字符串。这似乎真的是一个字体问题;打印字符串的各个字节会产生例如0x043f 0x0440 0x043e的结果,这显然不是“?”的Unicode代码。接受这个答案,非常感谢! - Frerich Raabe
1
Hans,我只想指出定义__WFILE__是技术上未定义的行为,因为它是一个保留符号(以两个下划线开头)。这可以通过使用任何其他名称(例如简单地使用WFILE)来定义宏来轻松解决。 - alecov
显示剩余3条评论

2

__FILE__ 会始终扩展为字符字符串字面量,因此本质上它将与char const*兼容。这意味着编译器实现没有太多选择,只能使用源文件名在编译时呈现的原始字节表示。

无论当前区域设置是否合理,您都可以拥有包含基本垃圾的源文件名,只要运行时系统和编译器将其视为有效文件名即可。

如果您作为用户具有不同于文件系统中使用的编码的不同区域设置,则会看到大量的????或类似内容。

但是,如果您的区域设置在编码方面达成一致,那么简单的printf就足够了,您的终端(或您用于查看输出的任何其他工具)应该能够正确打印字符。

因此,简短的答案是,仅当您的系统在编码方面保持一致时,它才能正常工作。否则,由于猜测编码是一项相当困难的任务,您将无法使用。


-1
关于编码,我猜测它是文件系统使用的编码,可能是Unicode。
至于处理它,你可以尝试将代码改为以下形式:
#define TRACE_BEACON activeLogFile()->write( FixThisString(__FILE__ )); 

std::string FixThisString(wchar_t* bad_string) { .....}

(实现FixThisString的方法留给学生作为练习。)

__FILE__ 是一个 char 字符串而不是 wchar_t 字符串。如果你想要这样做,你需要使用预处理器在其前缀加上L。然后你可以使用正确的printf系列函数来打印它。 - R.. GitHub STOP HELPING ICE
@R:他得到的错误是他要打印的字符串包含一个 '\u043F',这将是一个16位Unicode wchar_t。 - James Curran

-1
在MSVC中,您可以打开Unicode并获取UTF-16编码的字符串。它在项目属性中的某个地方。此外,您应该只使用wcout / cout而不是printf / wprintf。在Unicode存在之前,Windows需要Unicode,因此它们有一种自定义的多字节字符编码,这是默认值。但是,Windows支持UTF16-例如C#。
#include <iostream>

int main() {
    std::wcout << __WFILE__;
}

-1

最好的解决方案是使用可移植文件名字符集[A-Za-z0-9._-]中的源文件名。由于Windows不支持UTF-8,因此在普通字符串中表示任意非ASCII字符没有依赖于您配置的本地语言的方法。

gcc可能不关心;它将所有文件名视为8位字符串,因此如果文件名对gcc可访问,则其名称将是可表示的。(我知道cygwin默认提供UTF-8环境,并且现代*nix通常会是UTF-8。)对于MSVC,您可以尝试使用预处理器在__FILE__扩展前面添加L并使用%ls进行格式化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接