C: Печать больших номеров

Возьмите следующее:

#include <stdio.h>

main() {
    unsigned long long verybig = 285212672;

    printf("Without variable : %llu\n", 285212672);
    printf("With variable    : %llu", verybig);
}

Это результат выполнения вышеуказанной программы:

Without variable : 18035667472744448
With variable    : 285212672

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

Каковы причины этого?

Ответ 1

Попробуйте 285212672ULL; если вы напишете его без суффиксов, вы обнаружите, что компилятор рассматривает его как регулярное целое число. Причина, по которой она работает в переменной, состоит в том, что целочисленное значение передается в unsigned long long в присваивании, так что значение, переданное в printf(), является правильным типом.

И прежде чем вы спросите, нет, компилятор, вероятно, недостаточно умен, чтобы понять это из "%llu "в строке формата printf(). Это другой уровень абстракции. Компилятор отвечает за язык синтаксис, printf() семантика не является частью синтаксиса, это функция библиотеки времени выполнения (не отличается от ваших собственных функций, кроме того, что она включена в стандарт).

Рассмотрим следующий код для 32-битной int и 64-битной беззнаковой длинной длинной системы:

#include <stdio.h>

int main (void) {
    printf ("%llu\n",1,2);
    printf ("%llu\n",1ULL,2);
    return 0;
}

который выводит:

8589934593
1

В первом случае два 32-разрядных целых числа 1 и 2 помещаются в стек, а printf() интерпретирует это как одно 64-битное значение ULL, 2 x 2 32 + 1. Аргумент 2 непреднамеренно включается в значение ULL.

Во втором вы фактически нажимаете 64-битное 1-значение и избыточное 32-разрядное целое число 2, которое игнорируется.

Обратите внимание, что это "выход из строя" между вашей строкой формата и вашими фактическими аргументами - плохая идея. Что-то вроде:

printf ("%llu %s %d\n", 0, "hello", 0);

скорее всего, произойдет сбой, потому что 32-разрядный указатель "hello" будет потребляться %llu и %s попытается снять ссылку с конечного аргумента 0. Это иллюстрирует следующий "рисунок" (предположим, что ячейки 32 бита и что строка "hello" хранится в 0xbf000000.

What you pass     Stack frames     What printf() uses
                 +------------+
0                | 0          | \
                 +------------+  > 64-bit value for %llu.
"hello"          | 0xbf000000 | /
                 +------------+
0                | 0          |    value for %s (likely core dump here).
                 +------------+
                 | ?          |    value for %d (could be anything).
                 +------------+

Ответ 2

Стоит отметить, что некоторые компиляторы дают полезное предупреждение для этого случая - например, это то, что GCC говорит о вашем коде:

x.c: In function ‘main’:
x.c:6: warning: format ‘%llu’ expects type ‘long long unsigned int’, but argument 2 has type ‘int’

Ответ 3

285212672 - значение int. printf ожидает unsigned long long, и вы передаете его int. Следовательно, из стека потребуется больше байт, чем вы передали реальную ценность и печатает мусор. Когда вы помещаете его в переменную unsigned long long, прежде чем передавать ее функции, в строке назначения она будет повышена до unsigned long long, и вы передадите это значение в printf, которая работает правильно.

Ответ 4

Тип данных - это просто способ интерпретации содержимого ячейки памяти.
в первом случае постоянное значение сохраняется в ячейке памяти только для чтения в виде int, printf пытается интерпретировать этот адрес как 8-байтовое местоположение, поскольку ему дается указание, что сохраненное значение долгое время, в течение которого оно печатает значение мусора.
Во втором случае printf пытается интерпретировать длинное длинное значение как 8 байтов и печатает ожидаемое.