долго долго по сравнению с международным умножением

Учитывая следующий отрывок:

#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?), потому что мне нравится лучше понимать, почему и когда я должен бросить вручную (у меня есть проблемы при портировании программы от другой архитектуры).

7
задан azraiyl 16 August 2010 в 18:31
поделиться

4 ответа

Стандарт C99 действительно определяет, что двоичные операторы, такие как *, не работают с целочисленными типами меньше, чем int. Выражения этих типов переводятся в int перед применением оператора. См. параграф 2 пункта 6.3.1.4 и многочисленные вхождения слов "целочисленное продвижение". Но это несколько ортогонально к инструкциям ассемблера, генерируемым компилятором, которые работают с ints, потому что это быстрее, даже когда компилятору было бы позволено вычислить более короткий результат (потому что результат немедленно сохраняется в l-значении короткого типа, например).

Что касается int64 f = d * e; где d и e имеют тип int, то умножение выполняется как int в соответствии с теми же правилами продвижения. Переполнение технически является неопределенным поведением, здесь вы получаете результат с двумя слагаемыми, но в соответствии со стандартом вы могли бы получить что угодно.

Примечание: правила продвижения различают знаковые и беззнаковые типы при продвижении. Правило заключается в том, чтобы продвигать меньшие типы до int, если только int не может представлять все значения типа, в этом случае используется unsigned int.

7
ответ дан 6 December 2019 в 11:45
поделиться

Проблема в том, что умножение - это int32 * int32, которое выполняется как int32, а результат затем присваивается int64. Вы получите почти такой же эффект с double d = 3/2; , который разделит 3 на 2 с помощью целочисленного деления и присвоит 1,0 d .

Вы должны обращать внимание на тип выражения или части выражения, когда это может иметь значение. Для этого необходимо убедиться, что соответствующая операция рассчитана как соответствующий тип, например приведение одного из множимых к int64 или (в моем примере) 3.0 / 2 или (float) 3/2 .

5
ответ дан 6 December 2019 в 11:45
поделиться

a * b вычисляется как int, а затем приводится к типу принимающей переменной (который случайно оказался int)

d * e вычисляется как int, а затем приводится к типу принимающей переменной (который случайно оказался int64)

Если бы одна из переменных типа была больше int (или была с плавающей точкой), то использовался бы этот тип. Но поскольку все типы, используемые в умножениях, были int или меньше, использовались ints.

2
ответ дан 6 December 2019 в 11:45
поделиться

Прочитайте K&R (оригинал). Все целочисленные операции выполняются с натуральным целым типом, если только не используются переменные, которые являются (или приводятся) к чему-то большему. Операции над char приводятся к 32 битам, потому что это естественный размер целого числа на этой архитектуре. Умножение двух 32-битных целых чисел выполняется в 32 битах, потому что ничто не приводит его к чему-то большему (пока вы не присвоите его 64-битной переменной, но это уже слишком поздно). Если вы хотите, чтобы операция выполнялась в 64 битах, приведите одно или оба целых числа к 64 битам.

int64 f = (int64)d * e;
3
ответ дан 6 December 2019 в 11:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: