Ответ 1
В общем, не так уж сложно разместить необычные платформы для большинства случаев (если вы не хотите просто предполагать 8-битный char
, 2 дополнения, отсутствие прокладки, отсутствие ловушки и обрезку без знака в конец, подписанное преобразование), стандарт в основном дает достаточные гарантии (хотя несколько макросов для проверки некоторых деталей реализации были бы полезны).
Что касается строго соответствующей программы, которая может наблюдать (внешние бит-поля), то 5 всегда кодируется как 00...0101
. Это не обязательно физическое представление (что бы это ни значило), а то, что видно портативным кодом. Например, машине, использующей серый код, например, пришлось бы эмулировать "чистую двоичную нотацию" для побитовых операторов и сдвигов.
При отрицательных значениях подписанных типов допускаются разные кодировки, что приводит к разным (но четко определенным для каждого случая) результатам при повторной интерпретации в качестве соответствующего неподписанного типа. Например, строго соответствующий код должен различать (unsigned)n
и *(unsigned *)&n
для целого числа со знаком n
: они равны для двух дополнений без битов заполнения, но отличаются для других кодировок, если n
отрицательный.
Кроме того, могут существовать биты дополнений, а целые типы со знаком могут иметь больше битов заполнения, чем их соответствующие неподписанные копии (но не наоборот, тип-punning от подписанного до unsigned всегда действителен). sizeof
не может использоваться для получения количества битов без заполнения, так, например, для получения значения без знака, где установлен только знаковый бит (соответствующего типа подписки), необходимо использовать что-то вроде этого:
#define TYPE_PUN(to, from, x) ( *(to *)&(from){(x)} )
unsigned sign_bit = TYPE_PUN(unsigned, int, INT_MIN) &
TYPE_PUN(unsigned, int, -1) & ~1u;
(возможно, более приятные способы) вместо
unsigned sign_bit = 1u << sizeof sign_bit * CHAR_BIT - 1;
поскольку это может меняться больше, чем ширина. (Я не знаю о постоянном выражении, дающем ширину, но sign_bit
сверху можно смещать вправо до тех пор, пока не будет 0, чтобы определить его, Gcc может постоянно сгибать это.) Биты заполнения можно проверить с помощью memcpy
ing в массив unsigned char
, хотя они могут казаться "качающимися": чтение одного и того же бита заполнения дважды может дать разные результаты.
Если вам нужен битовый шаблон (без битов заполнения) целого числа со знаком (малое число):
int print_bits_u(unsigned n) {
for(; n; n>>=1) {
putchar(n&1 ? '1' : '0'); // n&1 never traps
}
return 0;
}
int print_bits(int n) {
return print_bits_u(*(unsigned *)&n & INT_MAX);
/* This masks padding bits if int has more of them than unsigned int.
* Note that INT_MAX is promoted to unsigned int here. */
}
int print_bits_2scomp(int n) {
return print_bits_u(n);
}
print_bits
дает разные результаты для отрицательных чисел в зависимости от используемого представления (он дает исходный бит-шаблон), print_bits_2scomp
дает представление двух дополнений (возможно, с большей шириной, чем a signed int
, если unsigned int
> имеет меньшее количество битов).
Необходимо проявлять осторожность, чтобы не генерировать ловушки при использовании побитовых операторов и при типом пиннинга от без знака до подписания см. ниже, как они могут быть сгенерированы (например, *(int *)&sign_bit
может ловушку с двумя дополнениями и -1 | 1
может ловушку с одним дополнением).
Целочисленное преобразование без знака в подпись (если преобразованное значение не представляется в целевом типе) всегда определяется реализацией, я ожидал бы, что машины с не-дополнением будут отличаться от общего определения более вероятным, хотя и технически, он также может стать проблемой для реализации с двумя дополнениями.
Из C11 (n1570) 6.2.6.2:
(1) Для целых чисел без знака, отличных от
unsigned char
, биты представления объекта должны быть разделены на две группы: биты значений и биты заполнения (их не должно быть ни одного из них). Если есть бит N, каждый бит должен представлять различную мощность 2 между 1 и 2 N-1, так что объекты этого типа должны быть способны представлять значения от 0 до 2 N -1 с использованием чистого двоичного представления; это должно быть известно как представление стоимости. Значения любых битов дополнений не определены.(2) Для знаковых целых типов биты представления объекта должны быть разделены на три группы: биты значений, биты заполнения и знаковый бит. Не должно быть никаких битов заполнения;
signed char
не должно иметь никаких битов заполнения. Должен быть ровно один знаковый бит. Каждый бит, который является битом значения, должен иметь то же значение, что и тот же бит, в представлении объекта соответствующего неподписанного типа (если в знаке значения Mтип и N в неподписанном типе, затем M≤N). Если знаковый бит равен нулю, он не должен влиять на результирующее значение. Если знаковый бит равен единице, значение должно быть изменено одним из следующих способов:
- соответствующее значение со знаком бит 0 отрицается (знак и величина);
- знаковый бит имеет значение - (2 M)(два дополнения);
- знаковый бит имеет значение - (2 M -1) (дополнение к ним).
Какое из них применяется, определяется реализацией, равно как и значение со знаковым битом 1 и всеми битами значений 0 (для первых двух) или со знакомным битом и всеми битами значения 1 (для одного дополнения) является представление ловушки или нормальное значение. В случае знака, величины и одного дополнения, если это представление является нормальным значением, оно называется отрицательным нулем.