Как напечатать строку, которую __FILE__ расширяет, чтобы правильно?

Рассмотрим эту программу:

#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, чтобы я мог корректно печатать его на любой другой системе во время выполнения (путем преобразования сохраненного представления UTF-16 в то, что использует система времени исполнения). Для этого мне нужно знать:

  • Какая кодировка используется для строки, заданной __FILE__? Кажется, что, по крайней мере, в Windows используется текущая системная кодовая страница (в моем случае, Windows-1252), но это просто гадание. Это правда?
  • Как я могу сохранить представление 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__ );

Это прерывается, если текущий исходный файл имеет имя, которое содержит символы, которые не могут быть представлены текущей кодовой страницей.

Ответ 1

Использование может использовать оператор склеивания токена, например:

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

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

Ответ 2

__FILE__ всегда будет расширяться до символьного строкового литерала, поэтому по существу он будет совместим с char const*. Это означает, что реализация компилятора не имеет иного выбора, кроме использования исходного байтового представления имени исходного файла, которое оно представляет во время компиляции.

Независимо от того, является ли это что-то разумное в текущей локали или нет, не имеет значения, вы могли бы иметь имя исходного файла, содержащее в основном мусор, если ваша система времени выполнения и компилятор принимают его как допустимое имя файла.

Если у вас, как у пользователя, есть другой язык с разной кодировкой, чем используется в вашей файловой системе, вы увидите много???? или аналогично.

Но если оба ваших сайта согласны с кодировкой, достаточно простого printf, и ваш терминал (или все, что вы используете для просмотра вывода) должен иметь возможность правильно печатать символы.

Итак, короткий ответ: он будет работать, только если ваша система совместима с кодировкой w.r.t. В противном случае вам не повезло, так как угадывание кодировок - довольно сложная задача.

Ответ 3

Что касается кодировки, я собираюсь угадать, что используется файловой системой, возможно, Unicode.

Что касается того, как это сделать, как "сменить код вам что-то вроде:

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

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

(Реализация FixThisString остается как упражнение для ученика.)

Ответ 4

Лучшим решением является использование исходных имен файлов в наборе символов переносимых файлов [A-Za-z0-9._-]. Поскольку Windows не поддерживает UTF-8, нет возможности для того, чтобы произвольные символы, отличные от ASCII, были представлены в обычных строках вне зависимости от вашего настроенного локального языка.

gcc, вероятно, не волнует; он обрабатывает все имена файлов как 8-битные строки, и поэтому, если имя файла доступно для gcc, его имя будет представлено. (Я знаю, что cygwin по умолчанию имеет среду UTF-8, а современный * nix обычно будет UTF-8.) Для MSVC вы могли бы использовать препроцессор для добавления L к расширению __FILE__ и использовать %ls, чтобы отформатировать его.

Ответ 5

В MSVC вы можете включить Unicode и получить кодированные строки UTF-16. Это где-то в свойствах проекта. Кроме того, вы должны просто использовать wcout/cout not printf/wprintf. Windows нуждалась в Unicode перед тем, как существовал Unicode, поэтому у них была пользовательская многобайтовая кодировка символов, которая по умолчанию. Однако Windows поддерживает UTF16- это, например, С#.

#include <iostream>

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