Подтвердить что ты не робот

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

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

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

Ответ 1

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

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


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

#include <stdio.h>
#include <sys/types.h>


int print_llu(unsigned long long ull)
{
  return printf("%llu\n", ull); // O.K.; already converted
}


int main()
{
  off_t a;

  printf("%llu\n", a); // WRONG! Undefined behavior!
  printf("%llu\n", (unsigned long long) a); // O.K.; explicit conversion
  print_llu((unsigned long long) a); // O.K.; explicit conversion
  print_llu(a); // O.K.; implicit conversion

  return 0;
}

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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