Предположим, у меня есть одно длинное длинное целое число, и я хочу взять его биты и построить из него четыре коротких целых числа без знака.
Конкретный порядок здесь особого значения не имеет.
Обычно я знаю, что мне нужно сдвинуть биты и усечь до размера unsigned short int. Но я думаю, что могу где-то совершить какую-то странную ошибку, поэтому спрашиваю.





#include <stdint.h>
#include <stdio.h>
union ui64 {
uint64_t one;
uint16_t four[4];
};
int
main()
{
union ui64 number = {0x123456789abcdef0};
printf("%x %x %x %x\n", number.four[0], number.four[1],
number.four[2], number.four[3]);
return 0;
}
Да, вы получаете разные результаты для систем с прямым порядком байтов и с прямым порядком байтов.
Это определенно быстрое и хакерское решение в том же порядке, что и: «@numbers = unpack 'S4', pack 'Q', $ number;» в Perl
Будет неприятно терпеть неудачу, например, Cray и TI DSP, чтобы назвать лишь несколько процессоров, у которых короткое / может быть 32 бита.
Я на 100% согласен с тем, что это решение вообще не переносимо. Возможно, мне следует изменить ответ, чтобы использовать inttypes.h. :-)
union LongLongIntToThreeUnsignedShorts {
long long int long_long_int;
unsigned short int short_ints[sizeof(long long int) / sizeof(short int)];
};
Это должно делать то, о чем вы думаете, без необходимости возиться с битовым смещением.
Можете ли вы гарантировать, что в структуре не будет отступов между короткими целыми числами?
Упс - изначально я писал о трех беззнаковых int. Это, вероятно, введет вас в заблуждение. Простите за это. В любом случае ваш ответ был полезен.
Первоначально это было <code> unsigned short int </code> (вероятно, по 2 байта - это нормально). Я хотел сказать, что эта структура опасна, если вы не уверены в правилах заполнения / выравнивания.
(unsigned short)((((unsigned long long int)value)>>(x))&(0xFFFF))
где value - это ваш long long int, а x - это 0, 16, 32 или 48 для четырех шорт.
Загвоздка со сдвигом битов заключается в том, что в C / C++ поведение отрицательных чисел не определено.
Для отрицательного «значения» оно не является неопределенным, оно определяется реализацией. Для отрицательного «x» он не определен, но здесь этого никогда не происходит. И формат хранения отрицательного целого числа также определяется реализацией (в пределах определенных возможностей), так что с этой точки зрения это не хуже, чем использование объединения.
Меня не волнует, расширяет ли знак сдвиг значение, я маскирую все, кроме нижних шестнадцати бит. Они четко определены.
Я думаю, что Александр указывает на то, что результат сдвига вправо для отрицательного количества со знаком не обязательно должен быть заполнен нулем или знаком. Это может быть что угодно, если компилятор документирует что. По крайней мере, на C++. Стандарт C. Я тоже не проверял. Однако по поводу «почти всех» компиляторов вы правы.
Вы меня поняли: правильная формулировка стандарта - «определяется реализацией». Я думаю, что самое простое решение для решения в этом ответе - сначала преобразовать значение в беззнаковые типы. Тогда решение превосходит структуры, потому что оно дает одинаковые результаты для архитектур с прямым и обратным порядком байтов.
«определяется реализацией» означает, что компилятор C++ в принципе может генерировать код, который, скажем, ничего не делает, если левая часть оператора сдвига вправо отрицательна.
Это позаботится, по крайней мере, о проблеме little / big-endian в ответах на основе объединения
Я понимаю, что на самом деле в большинстве компиляторов сдвиг вправо либо заполняет левые биты значением 0, либо распространяет знак, но лучше на всякий случай сначала преобразовать «значение» в беззнаковое.
Да, я бы тоже так сделал - преобразование со знаком -> без знака определено (по крайней мере, в C++, я еще не проверил C). Таким образом, результат везде одинаковый, при условии, что типы имеют ожидаемые значения MIN / MAX.
Значит, отредактировал, чего стоит. Хотя я никогда не встречал реализации, которая делала бы что-либо, кроме расширения знака, я думаю, что лучше перестраховаться.
@SteveJessop: Интересно, есть ли какая-то особая причина, по которой правила Стандарта для смещения отрицательных чисел вправо или принуждения больших значений к малоразмерным типам со знаком не определяют никаких поведенческих требований? Насколько я могу судить, для реализации было бы вполне законно указать, что для всех отрицательных x x>>y даст 42 или y, или вычислит число любым способом, который нравится автору реализации, без побочных эффектов, x>>y с отрицательным x по существу бесполезен в строго соответствующем коде, даже если такой код будет одинаково доволен ...
... любые средства оценки, которые могла бы правдоподобно использовать любая "удаленно-нормальная" реализация.
@supercat: Я полагаю, это потому, что они не хотели предотвращать реализации, просто используя код операции сдвига архитектуры, и по какой-то причине не были уверены, что могут предоставить список опций, который будет включать все «удаленно нормальные» текущие и будущие архитектуры. Текущая ситуация такова, что эти строго соответствующие программы должны проверять, является ли целое число отрицательным перед сдвигом, если программист не знает, что это неотрицательно. В любой архитектуре, где код операции сдвига не обеспечивает согласованного поведения, компилятор должен будет выдать код для проверки всех сдвигов.
@SteveJessop: имеет смысл определять поведение для архитектур с дополнением единиц или знаковой величиной, поскольку есть преимущества в том, что реализация действует на представление числа, но есть также преимущества в вычислении того же значения, что и архитектура с дополнением до двух, когда это возможно (приведение uint-to-int-to-uint в соответствии с такими правилами будет возвращать все значения, кроме UINT_MAX / 2, а подписанный сдвиг вправо будет предлагать семантику полового деления, которая отличается от таковой для подписанного деление способами, которые часто бывают полезными). Также могут быть случаи ...
... где альтернативные вычисления могут быть полезны в реализации, которая использует математику с дополнением до двух, но имеет разное заполнение для подписанных и беззнаковых чисел. Однако для реализации, в которой используются целые числа с дополнением до двух без заполнения, единственным правдоподобным вариантом, который я мог представить в аппаратном обеспечении, было бы отсутствие сдвига вправо с расширением со знаком (и я видел подобное оборудование), и даже если знак -расширение сдвига вправо требовало большего количества кода, чем расширение без знака, я считаю, что x = (int)((unsigned)x>>1) не менее понятен, чем x>>1 в случае, когда ...
... семантика нулевого заполнения была подходящей. Я согласен с тем, что в соответствии с настоящим стандартом, код, который хочет выполнить сдвиг вправо с расширенным знаком, должен пройти через некоторые обручи, чтобы выполнить это. Мой вопрос в том, послужит ли какой-либо полезной цели то, что Стандарт написан таким образом. На мой взгляд, если более 90% компиляторов позволят написать операцию более ясным образом (а на некоторых компиляторах, вероятно, также будут работать более эффективно), чем любой строго соответствующий способ ее написания, Стандарт должен признавать что в качестве нормативного, определите макрос, указывающий ...
... соблюдает ли компилятор это соглашение и определяет условия, связанные с соответствием, для программ, которые будут работать на всех компиляторах, которые придерживаются указанных нормативных соглашений, даже если они не будут работать со всеми компиляторами. Например. «Программа X строго соответствует требованиям при использовании с компиляторами, соответствующими нормативным стандартам целочисленной семантики типа 2, семантики указателя типа 3 и размера int 32 бита или больше». Увы, дела идут в обратном направлении; Авторы компиляторов мало заинтересованы в поддержке конструкций, которые работали бы в 99% компиляторов, написанных с 1989 по 2009 год.
Будет ли на это влиять прямой / прямой порядок байтов?