Подтвердить что ты не робот

Эффективный способ битовой копии целого числа со знаком с целым числом без знака

/* [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]? Эффективно ли он достигает того, что я намерен?

4b9b3361

Ответ 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).