Эффективный способ битовой копии целого числа со знаком с целым числом без знака
/* [1] */
int i = -1;
unsigned u = (unsigned)i;
/* [2] */
int i = -1;
unsigned u;
memcpy(&u, &i, sizeof i);
/* [3] */
int i = -1;
unsigned u = *(unsigned *)&i;
Чтобы скопировать целое число со знаком с его неподписанным партнером, [1]
должно работать на большинстве машин, но насколько я знаю, это не гарантированное поведение.
[2]
должен делать именно то, что я хочу, но я хочу избежать накладных расходов на вызов библиотечной функции.
Итак, как насчет [3]
? Эффективно ли он достигает того, что я намерен?
Ответы
Ответ 1
[3] корректен как для C, так и для С++ (как и для С++ 14, но не ранее); в этом случае нет необходимости использовать memcpy
. (Тем не менее, нет причин не использовать memcpy
, поскольку он эффективно передает ваши намерения, очевидно, безопасен и имеет нулевые накладные расходы.)
C, 6.5 Выражения:
7 - Объект должен иметь сохраненное значение, доступ к которому можно получить только с помощью выражения lvalue, которое имеет один из следующие типы: [...]
- тип, который является подписанным или неподписанным типом, соответствующим эффективному типу объект, [...]
С++, [basic.lval]:
10 - Если программа пытается получить доступ к сохраненному значению объекта через значение gl, отличное от одного из следующие типы: undefined: [...]
- тип, который является подписанным или неподписанным типом, соответствующим динамическому типу объекта, [...]
Как вы можете видеть, формулировка в двух стандартах очень похожа, и поэтому на них можно положиться на двух языках.
Ответ 2
/* [4] */
union unsigned_integer
{
int i;
unsigned u;
};
unsigned_integer ui;
ui.i = -1;
// You now have access to ui.u
Предупреждение: Как обсуждалось в комментариях, это выглядит нормально в C
и Undefined Behavior in C++
, так как ваш вопрос имеет оба тега, я оставлю это здесь. Для получения дополнительной информации проверьте этот вопрос:
Доступ к неактивному члену профсоюза и поведению Undefined?
Тогда я бы посоветовал reinterpret_cast
в C++
:
/* [5] */
int i = -1;
unsigned u = reinterpret_cast<unsigned&>(i);
Ответ 3
/* [1] */
int i = -1;
unsigned u = (unsigned)i;
↑ Это гарантированно не работает на машине с знаками и величинами или 1 дополнением, потому что преобразование в беззнаковое гарантированно даст подписанное значение по модулю 2 n где n - это число значений биты представления в неподписанном типе. То есть конверсия гарантированно даст тот же результат, что и в случае, когда подписанный тип использовал два дополнительных представления.
/* [2] */
int i = -1;
unsigned u;
memcpy(&u, &i, sizeof i);
↑ Это будет работать хорошо, потому что типы гарантированно имеют одинаковый размер.
/* [3] */
int i = -1;
unsigned u = *(unsigned *)&i;
↑ Это формально Undefined Поведение в С++ 11 и более ранних версиях, но это один из случаев, включенных в предложение "строгое псевдонижение" в стандарте, и поэтому он, вероятно, поддерживается каждым существующим компилятором. Кроме того, это пример того, для чего существует reinterpret_cast
. И в С++ 14 и более поздних версиях из (1) был выведен язык о Undefined в разделе о преобразовании lvalue в rvalue.
Если бы я сделал это, я использовал бы ядро С++ для ясности.
Я бы, однако, попытался выяснить, что должны сказать о компиляторах иногда выглядят стандартно-позволяет-мне-делать-непрактичные вещи, в частности g++ с его строгим вариантом псевдонимов, независимо от того, что это такое, но также и clang, поскольку он был разработан как замена для замены g++.
По крайней мере, если я планировал использовать код с этими компиляторами и параметрами.
1) [conv.lval], §4.1/1 как в С++ 11, так и в С++ 14.
Ответ 4
Это из параграфа 4.7 "Интегральные преобразования" документа N3797, последнего рабочего проекта стандарта С++ 14:
Если тип назначения не указан, результирующее значение является наименьшим беззнаковое целое, совпадающее с целым числом источника (по модулю 2 n где n равно количество бит, используемых для представления неподписанного типа). [Примечание: в двухкомпонентное представление, это преобразование является концептуальным и в битовой схеме нет изменений (если нет усечения). -end note]
В первом приближении все компьютеры в мире используют два дополнительных представления. Итак, [1] - это путь (если вы не переносите С++ на IBM 7090).