Почему в printf требуется печать?

Чтобы напечатать несколько типов off_t было рекомендовано использовать следующий fragment кода:

 off_t a; printf("%llu\n", (unsigned long long)a); 
  • Почему строки формата недостаточно?
  • Какая проблема, если бы она не была брошена?

Строка формата не сообщает компилятору о том, чтобы выполнить приведение в unsigned long long , он просто сообщает printf что он будет получать unsigned long long . Если вы передадите то, что unsigned long long время не является unsigned long long (что off_t может и не быть), printf просто неправильно интерпретирует его, с неожиданными результатами.

Причина этого в том, что компилятор не должен ничего знать о строках формата. Хороший компилятор даст вам предупреждающее сообщение, если вы пишете printf("%d", 3.0) , но что может сделать компилятор, если вы пишете printf(s, 3.0) , причем s является строкой, определяемой динамически во время выполнения?


Отредактировано для добавления: Как указывает Кит Томпсон в комментариях ниже, есть много мест, где компилятор может выполнить такое неявное преобразование. printf является весьма исключительным, в одном случае, когда он не может . Но если вы объявите функцию для принятия unsigned long long , тогда компилятор выполнит преобразование:

 #include  #include  int print_llu(unsigned long long ull) { return printf("%llu\n", ull); // OK; already converted } int main() { off_t a; printf("%llu\n", a); // WRONG! Undefined behavior! printf("%llu\n", (unsigned long long) a); // OK; explicit conversion print_llu((unsigned long long) a); // OK; explicit conversion print_llu(a); // OK; implicit conversion return 0; } 

Причина этого заключается в том, что printf объявляется как int printf(const char *format, ...) , где ... представляет собой «переменную» или «переменную аргументы», сообщающую компилятору, что он может принимать любое число и типы аргументов после format . (Очевидно, что printf не может принимать какие-либо числа и типы аргументов: он может принимать только число и типы, о которых вы рассказываете, используя format . Но компилятор ничего об этом не знает, остаётся программисту обрабатывать Это.)

Даже с ... , компилятор делает некоторые неявные преобразования, такие как продвижение char для int и float чтобы double . Но эти преобразования не относятся к printf , и они не зависят от строки формата и не могут.

Проблема в том, что вы не знаете, насколько большой off_t. Это может быть 64-разрядный тип или 32-битный тип (или, возможно, что-то еще). Если вы используете% llu и не проходите длинный длинный тип (без знака), вы получите неопределенное поведение, на практике это может просто напечатать мусор.

Не зная, насколько это велика, легкий выход – это использовать его для самых разумных типов, поддерживаемых вашей системой, например, без знака долго. Таким образом, использование% llu безопасно, так как printf получит неподписанный длинный длинный тип из-за приведения.

(например, в linux размер off_t по умолчанию 32 бит на 32-битной машине и 64 бит, если вы включите поддержку больших файлов через #define _FILE_OFFSET_BITS=64 прежде чем включать соответствующие заголовки системы)

Подпись printf выглядит так:

 int printf(const char *format, ...); 

Параметр vararg ... указывает, что все может следовать, и по правилам C вы можете передать что-либо в printf пока вы включаете строку формата. C просто не имеет никаких конструкций для описания каких-либо ограничений для типов переданных объектов. Вот почему вы должны использовать броски, чтобы переданные объекты имели точно необходимый тип.

Это типично для C, он проводит линию между жесткостью и доверяет программисту. Несвязанный пример состоит в том, что вы можете использовать char * (без const ) для ссылки на строковые литералы, но если вы их модифицируете, ваша программа может потерпеть крах.