Учитывая следующий отрывок:
#include <stdio.h>
typedef signed long long int64;
typedef signed int int32;
typedef signed char int8;
int main()
{
printf("%i\n", sizeof(int8));
printf("%i\n", sizeof(int32));
printf("%i\n", sizeof(int64));
int8 a = 100;
int8 b = 100;
int32 c = a * b;
printf("%i\n", c);
int32 d = 1000000000;
int32 e = 1000000000;
int64 f = d * e;
printf("%I64d\n", f);
}
Вывод с MinGW GCC 3.4.5 (-O0):
1
4
8
10000
-1486618624
Первое умножение является литым к int32 внутренне (согласно ассемблерному выводу). Второе умножение не является литым. Я не уверен, отличаются ли результаты, потому что программа работала на IA32, или потому что она определяется где-нибудь в стандарте C. Тем не менее, мне интересно, если это точное поведение определяется где-нибудь (ISO/IEC 9899?), потому что мне нравится лучше понимать, почему и когда я должен бросить вручную (у меня есть проблемы при портировании программы от другой архитектуры).
Стандарт C99 действительно определяет, что двоичные операторы, такие как *
, не работают с целочисленными типами меньше, чем int
. Выражения этих типов переводятся в int
перед применением оператора. См. параграф 2 пункта 6.3.1.4 и многочисленные вхождения слов "целочисленное продвижение". Но это несколько ортогонально к инструкциям ассемблера, генерируемым компилятором, которые работают с int
s, потому что это быстрее, даже когда компилятору было бы позволено вычислить более короткий результат (потому что результат немедленно сохраняется в l-значении короткого типа, например).
Что касается int64 f = d * e;
где d
и e
имеют тип int
, то умножение выполняется как int
в соответствии с теми же правилами продвижения. Переполнение технически является неопределенным поведением, здесь вы получаете результат с двумя слагаемыми, но в соответствии со стандартом вы могли бы получить что угодно.
Примечание: правила продвижения различают знаковые и беззнаковые типы при продвижении. Правило заключается в том, чтобы продвигать меньшие типы до int
, если только int
не может представлять все значения типа, в этом случае используется unsigned int
.
Проблема в том, что умножение - это int32 * int32, которое выполняется как int32, а результат затем присваивается int64. Вы получите почти такой же эффект с double d = 3/2;
, который разделит 3 на 2 с помощью целочисленного деления и присвоит 1,0 d
.
Вы должны обращать внимание на тип выражения или части выражения, когда это может иметь значение. Для этого необходимо убедиться, что соответствующая операция рассчитана как соответствующий тип, например приведение одного из множимых к int64 или (в моем примере) 3.0 / 2
или (float) 3/2
.
a * b
вычисляется как int, а затем приводится к типу принимающей переменной (который случайно оказался int)
d * e
вычисляется как int, а затем приводится к типу принимающей переменной (который случайно оказался int64)
Если бы одна из переменных типа была больше int (или была с плавающей точкой), то использовался бы этот тип. Но поскольку все типы, используемые в умножениях, были int или меньше, использовались ints.
Прочитайте K&R (оригинал). Все целочисленные операции выполняются с натуральным целым типом, если только не используются переменные, которые являются (или приводятся) к чему-то большему. Операции над char приводятся к 32 битам, потому что это естественный размер целого числа на этой архитектуре. Умножение двух 32-битных целых чисел выполняется в 32 битах, потому что ничто не приводит его к чему-то большему (пока вы не присвоите его 64-битной переменной, но это уже слишком поздно). Если вы хотите, чтобы операция выполнялась в 64 битах, приведите одно или оба целых числа к 64 битам.
int64 f = (int64)d * e;