2014-11-18 2 views
40

Так что я об этом коде:32 бит без знака умножаются на 64 бит, вызывая неопределенное поведение?

uint32_t s1 = 0xFFFFFFFFU; 
uint32_t s2 = 0xFFFFFFFFU; 
uint32_t v; 
... 
v = s1 * s2; /* Only need the low 32 bits of the result */ 

Во всех Подписках я предполагаю, что компилятор не может иметь какие-либо предубеждений на диапазоне s1 или s2, инициализаторы служат только для примера выше.

Если я скомпилировал это в компиляторе с целым размером 32 бита (например, при компиляции для x86), никаких проблем. Компилятор просто использовал бы s1 и s2 как uint32_t введенные значения (не будучи в состоянии продвинуть их дальше), и умножение просто даст результат, как говорится в комментарии (по модулю UINT_MAX + 1, который равен 0x100000000 в этом случае).

Однако, если я скомпилировал это в компиляторе с целым размером 64 бит (например, для x86-64), может быть неопределенное поведение из того, что я могу вывести из стандарта C. Целочисленное продвижение по службе будет выглядеть так: uint32_t может быть увеличено до int (64-разрядная подпись), умножение затем попытается умножить два int, которые, если они имеют значения, показанные в примере, вызовут переполнение целых чисел, что неопределенное поведение.

Я исправлю это, и если да, как бы вы избегали его разумным способом?

Я заметил этот вопрос, который похож, но охватывает C++: What's the best C++ way to multiply unsigned integers modularly safely?. Здесь я хотел бы получить ответ, применимый к C (предпочтительно совместимый с C89). Я бы не подумал о том, чтобы сделать плохую 32-битную машину, потенциально выполняющую 64-битное умножение приемлемого ответа, хотя (как правило, в коде, где это было бы опасно, 32-разрядная производительность может быть более критичной, как правило, это более медленные машины).

Обратите внимание, что эта же проблема может быть применена к 16-битным беззнаковым ints при компиляции с компилятором, имеющим 32-разрядный размер int или unsigned chars при компиляции с компилятором, имеющим размер 16 бит int (последний может быть общим с компиляторами для 8-битных ЦП: стандарт C требует, чтобы целые числа составляли не менее 16 бит, поэтому вероятно, что соответствующий компилятор будет затронут).

+6

'int' 32 бит даже на большинстве современных 64-разрядных архитектур http://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models –

+0

Что заставляет вас думать, что вы определили поведение на 32-битной машина? –

+0

Первоначально я написал это 16 бит против 32 бит, но потом понял, что сегодня может быть более практичным сравнение 32 против 64 бит. Чарльз: Я предполагаю, анализируя стандарт C89. Возможно, я ошибаюсь, а затем указываю, в чем! Это также приемлемый результат/ответ! – Jubatian

ответ

27

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

v = 1U * s1 * s2; 

Это либо преобразует 1U в uint32_t или s1 и s2 к unsigned int, в зависимости от того, что подходит для вашей конкретной платформы.

@Deduplicator отмечает, что некоторые компиляторы, где uint32_t является более узким, чем unsigned int, может предупредить о неявном в назначении, и отмечает, что такие предупреждения могут suppressable путем преобразования явного:

v = (uint32_t) (1U * s1 * S2); 

It выглядит немного менее изящным, на мой взгляд.

+1

+1 Да, я думал слишком сложно. – Deduplicator

+0

@Deduplicator Это происходит неявно, так как 'v' определяется как' uint32_t'. – hvd

+0

Да, этот ответ кажется довольно изящным ... Кого выбрать, который я должен выбрать ... :) – Jubatian

10

Поздравляем с нахождением точки трения.

Возможный способ:

v = (uint32_t) (UINT_MAX<=0xffffffff 
    ? s1 * s2 
    : (unsigned)s1 * (unsigned)s2); 

Во всяком случае, похоже, добавив несколько определений типов в <stdint.h> для типов гарантированно быть не меньше int не было бы в порядке ;-).

+0

Нужно ли условное условие? Я предполагаю, что он будет играть нормально как просто 'v = (unsigned) s1 * (unsigned) s2;', тип 'v' позаботится о правильном усечении в любом случае, в то время как на 32 битах он все равно будет 32-битным умножением , По крайней мере, если я, кроме 'unsigned', должен быть не менее 32 бит ... Подождите, не существуют ли такие типы, которые уже определены? – Jubatian

+0

Проблема заключается в том, что 'int' не должен иметь более 16 бит ... И нет, таких типов нет. – Deduplicator

+1

Эх, извините за туманность, я имел в виду 32-битный компилятор int ... Ну, тип, который я имею в виду, это 'uint_least32_t' в' stdint.h', подходящая подстановка может быть даже ifdeffed для C89, я думаю. – Jubatian