В C оператор sizeof возвращает 8 байтов при передаче 2,5 м, но 4 байта при прохождении 1,25 м * 2

Я не понимаю, почему оператор sizeof производит следующие результаты:

sizeof( 2500000000 ) // => 8 (8 bytes).

... он возвращает 8, и когда я делаю следующее:

sizeof( 1250000000 * 2 ) // => 4 (4 bytes).

... он возвращает 4, а не 8 (что я и ожидал). Может ли кто-нибудь уточнить, как sizeof определяет размер выражения (или типа данных) и почему в моем конкретном случае это происходит?

Мое лучшее предположение состоит в том, что оператор sizeof является оператором времени компиляции.

Вопрос о Bounty: Есть ли оператор времени выполнения, который может оценивать эти выражения и производить ожидаемый результат (без кастинга)?

Ответ 1

2500000000 не подходит для int, поэтому компилятор правильно интерпретирует его как long (или long long или тип, в котором он подходит). 1250000000, и т.д. 2. Параметр sizeof не оценивается, поэтому компилятор не может знать, что умножение не соответствует int, и поэтому возвращает размер int.

Кроме того, даже если этот параметр был оценен, вы, вероятно, получите переполнение (и поведение undefined), но, вероятно, все еще приводят к 4.

Здесь:

#include <iostream>
int main()
{
    long long x = 1250000000 * 2;
    std::cout << x;
}

Вы можете угадать результат? Если вы думаете, что это 2500000000, вы ошибаетесь. Тип выражения 1250000000 * 2 равен int, потому что операнды int и int, и умножение не автоматически продвигается к большему типу данных, если он не подходит.

http://ideone.com/4Adf97

Итак, gcc говорит, что это -1794967296, но это поведение undefined, так что это может быть любое число. Это число вписывается в int.

Кроме того, если вы добавили один из операндов к ожидаемому типу (так же, как вы добавляете целые числа при делении, если вы ищете нецелочисленный результат), вы увидите, что это работает:

#include <iostream>
int main()
{
    long long x = (long long)1250000000 * 2;
    std::cout << x;
}

дает правильный 2500000000.

Ответ 2

[Edit: Я не заметил, изначально, что это было опубликовано как C и С++. Я отвечаю только по поводу C.]

Отвечая на ваш следующий вопрос: "В любом случае, чтобы определить объем памяти, выделенный для выражения или переменной во время выполнения?": ну, не совсем. Проблема в том, что это не очень хорошо сформулированный вопрос.

"Выражения" на языке C (в отличие от некоторой конкретной реализации) фактически не используют память. (Конкретные реализации нуждаются в некоторой памяти кода и/или памяти для проведения вычислений, в зависимости от того, сколько результатов будет вписываться в регистры процессора и т.д.). Если результат выражения не спрятан в переменной, он просто исчезает (и компилятор может часто опускать код времени выполнения для вычисления никогда не сохраняемого результата). Язык не дает вам способ спросить о том, чего он не предполагает, т.е. Пространства для хранения выражений.

Переменные, с другой стороны, занимают память (память). Объявление переменной указывает компилятору, сколько памяти следует отложить. Однако, за исключением массивов переменной длины C99, требуемое хранилище определяется исключительно во время компиляции, а не во время выполнения. Вот почему sizeof x обычно является константным выражением: компилятор может (и на самом деле должен) определять значение sizeof x во время компиляции.

C99 VLA являются особым исключением из правила:

void f(int n) {
    char buf[n];
    ...
}

Хранение, требуемое для buf, не является (вообще) чем-то, что компилятор может найти во время компиляции, поэтому sizeof buf не является константой времени компиляции. В этом случае buf фактически выделяется во время выполнения, и его размер определяется только тогда. Таким образом, sizeof buf представляет собой вычисляемое по времени выражение.

В большинстве случаев все зависит от размера, во время компиляции, и если выражение переполняется во время выполнения, поведение undefined, определяется реализацией или четко определено в зависимости от типа. Подписанное целочисленное переполнение, так как в 2,5 миллиарда умноженное на 2, когда INT_MAX составляет чуть более 2,7 миллиарда, результат "undefined поведение". Беззнаковые целые числа имеют модульную арифметику и, следовательно, позволяют вычислять в GF (2 k).

Если вы хотите убедиться, что какой-то расчет не может переполняться, это то, что вам нужно рассчитать самостоятельно во время выполнения. Это большая часть того, что трудно записывать в C-библиотеках multiprecision (например, gmp), как правило, намного проще и быстрее, чтобы кодировать большие части этого в сборке и использовать известные свойства CPU (например, флаги переполнения или двойные пары результирующих реестров).

Ответ 3

Лучиан ответил на это уже. Просто для его завершения.

Стандартные состояния C11 (стандарт С++ имеет похожие строки), что тип целочисленного литерала без суффикса для обозначения типа прерывается следующим образом:

Из 6.4.4 Константы ( Проект C11 ):

Семантика

4 Значение десятичной константы вычисляется базой 10; что восьмеричная константа, основание 8; шестнадцатеричной константы, базы 16. лексически первая цифра является наиболее значимой.

5 Тип целочисленной константы является первым из соответствующих  список, в котором может быть представлено его значение.

И таблица выглядит следующим образом:

Десятичная константа

int
int long int 
long long int

Октальная или шестнадцатеричная константа

int
unsigned int
long int
unsigned long int
long long int
unsigned long long int

Для констант Octal и Hexadecimal возможны даже неподписанные типы. Поэтому в зависимости от вашей платформы в зависимости от того, что в приведенном выше списке (int или long int или long long int) подходит первым (в порядке), будет типом целочисленного литерала.

Ответ 4

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

Как и в других плакатах, существует исключение для массива переменной длины (размер которого известен только во время выполнения).

Другими словами, вы обычно пишете такие вещи, как sizeof(type) или sizeof expression, где выражение L-Value. Выражение почти никогда не является сложным вычислением (например, глупым примером вызова функции выше): в любом случае это было бы бесполезно, поскольку оно не оценивалось.

#include <stdio.h>

int main(){
    struct Stype {
            int a;
    } svar;
    printf("size=%d\n", sizeof(struct Stype));
    printf("size=%d\n", sizeof svar);
    printf("size=%d\n", sizeof svar.a);
    printf("size=%d\n", sizeof(int));

}

Также обратите внимание, что поскольку sizeof - это ключевое слово языка, перед конечным выражением не требуется скобка функции (у нас есть такое же правило для ключевого слова return).

Ответ 5

Для вашего последующего вопроса нет "оператора", и нет никакой разницы между размером "времени компиляции" выражения и размером "времени выполнения".

Если вы хотите узнать, может ли данный тип сохранить результат, который вы ищете, вы всегда можете попробовать что-то вроде этого:

#include <stdio.h>
#include <limits.h>

int main(void) {
    int a = 1250000000;
    int b = 2;

    if ( (INT_MAX / (double) b) > a ) {
        printf("int is big enough for %d * %d\n", a, b);
    } else {
        printf("int is not big enough for %d * %d\n", a, b);
    }

    if ( (LONG_MAX / (double) b) > a ) {
        printf("long is big enough for %d * %d\n", a, b);
    } else {
        printf("long is not big enough for %d * %d\n", a, b);
    }

    return 0;
}

и (слегка) более общее решение, только для жаворонков:

#include <stdlib.h>
#include <stdio.h>
#include <limits.h>

/* 'gssim' is 'get size of signed integral multiplication */

size_t gssim(long long a, long long b);
int same_sign(long long a, long long b);

int main(void) {
    printf("size required for 127 * 1 is %zu\n", gssim(127, 1));
    printf("size required for 128 * 1 is %zu\n", gssim(128, 1));
    printf("size required for 129 * 1 is %zu\n", gssim(129, 1));
    printf("size required for 127 * -1 is %zu\n", gssim(127, -1));
    printf("size required for 128 * -1 is %zu\n", gssim(128, -1));
    printf("size required for 129 * -1 is %zu\n", gssim(129, -1));
    printf("size required for 32766 * 1 is %zu\n", gssim(32766, 1));
    printf("size required for 32767 * 1 is %zu\n", gssim(32767, 1));
    printf("size required for 32768 * 1 is %zu\n", gssim(32768, 1));
    printf("size required for -32767 * 1 is %zu\n", gssim(-32767, 1));
    printf("size required for -32768 * 1 is %zu\n", gssim(-32768, 1));
    printf("size required for -32769 * 1 is %zu\n", gssim(-32769, 1));
    printf("size required for 1000000000 * 2 is %zu\n", gssim(1000000000, 2));
    printf("size required for 1250000000 * 2 is %zu\n", gssim(1250000000, 2));

    return 0;
}

size_t gssim(long long a, long long b) {
    size_t ret_size;
    if ( same_sign(a, b) ) {
        if ( (CHAR_MAX / (long double) b) >= a ) {
            ret_size = 1;
        } else if ( (SHRT_MAX / (long double) b) >= a ) {
            ret_size = sizeof(short);
        } else if ( (INT_MAX / (long double) b) >= a ) {
            ret_size = sizeof(int);
        } else if ( (LONG_MAX / (long double) b) >= a ) {
            ret_size = sizeof(long);
        } else if ( (LLONG_MAX / (long double) b) >= a ) {
            ret_size = sizeof(long long);
        } else {
            ret_size = 0;
        }
    } else {
        if ( (SCHAR_MIN / (long double) llabs(b)) <= -llabs(a) ) {
            ret_size = 1;
        } else if ( (SHRT_MIN / (long double) llabs(b)) <= -llabs(a) ) {
            ret_size = sizeof(short);
        } else if ( (INT_MIN / (long double) llabs(b)) <= -llabs(a) ) {
            ret_size = sizeof(int);
        } else if ( (LONG_MIN / (long double) llabs(b)) <= -llabs(a) ) {
            ret_size = sizeof(long);
        } else if ( (LLONG_MIN / (long double) llabs(b)) <= -llabs(a) ) {
            ret_size = sizeof(long long);
        } else {
            ret_size = 0;
        }
    }
    return ret_size;
}

int same_sign(long long a, long long b) {
    if ( (a >= 0 && b >= 0) || (a <= 0 && b <= 0) ) {
        return 1;
    } else {
        return 0;
    }
}

который в моей системе выводит:

size required for 127 * 1 is 1
size required for 128 * 1 is 2
size required for 129 * 1 is 2
size required for 127 * -1 is 1
size required for 128 * -1 is 1
size required for 129 * -1 is 2
size required for 32766 * 1 is 2
size required for 32767 * 1 is 2
size required for 32768 * 1 is 4
size required for -32767 * 1 is 2
size required for -32768 * 1 is 2
size required for -32769 * 1 is 4
size required for 1000000000 * 2 is 4
size required for 1250000000 * 2 is 8

Ответ 6

Да, sizeof() не вычисляет память, необходимую для результата этого умножения.

Во втором случае для обоих литералов: 1250000000 и 2 требуется 4 bytes памяти, поэтому sizeof() возвращает 4. Если одно из значений было выше 4294967295 (2^32 - 1), вы получили бы 8.

Но я не знаю, как sizeof() вернул 8 для 2500000000. Он возвращает 4 в моем компиляторе VS2012

Ответ 7

Проект C11 здесь: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf Здесь вы можете найти черновик Cx0: http://c0x.coding-guidelines.com/6.5.3.4.html

В обоих случаях раздел 6.5.3.4 - это то, что вы ищете. В основном, ваша проблема сводится к следующему:

// Example 1:
long long x = 2500000000;
int size = sizeof(x); // returns 8

// Example 2:
int x = 1250000000;
int y = 2;
int size = sizeof(x * y); // returns 4

В примере 1 у вас есть long long (8 байт), поэтому он возвращает 8. В примере 2 у вас есть int * int, который возвращает int, который равен 4 байтам (поэтому он возвращает 4).

Отвечайте на свой вопрос: Да и нет. sizeof не рассчитает размер, необходимый для операции, которую вы пытаетесь выполнить, но он укажет размер результатов, если вы выполните операцию с соответствующими метками:

long long x = 1250000000;
int y = 2;
int size = sizeof(x * y); // returns 8

// Alternatively
int size = sizeof(1250000000LL * 2); // returns 8

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

Ответ 8

Самый простой ответ в одной строке:

sizeof() - это функция, вычисляемая в COMPILE TIME, кто вводит тип c, значение которого полностью игнорируется

ДАЛЬНЕЙШАЯ ДЕТАЛИ:.. поэтому, когда скомпилировано 2500000000, он должен быть сохранен как LONG, поскольку он слишком длинный, чтобы вписаться в int, поэтому этот аргумент просто скомпилирован как '(type) long'. Тем не менее, 1250000000 и 2 оба соответствуют типу 'int', поэтому это тип, передаваемый sizeof, так как результирующее значение никогда не сохраняется, поскольку компилятор просто интересуется типом, умножение никогда не оценивается.