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

Как сделать небольшое представление стандартным способом C?

В соответствии со стандартом C представление значения целочисленного типа определяется реализацией. Поэтому 5 может не отображаться как 00000000000000000000000000000101 или -1 как 11111111111111111111111111111111, как мы обычно предполагаем в 32-битном дополнении 2. Поэтому, хотя операторы ~, << и >> четко определены, битовые шаблоны, над которыми они будут работать, определяются реализацией. Единственный определенный битовый шаблон, который я мог найти, был "§5.2.1/3. Байт со всеми битами, установленными в 0, называемый нулевым символом, должен существовать в базовом наборе символов выполнения, он используется для завершения символьной строки".

Итак, мои вопросы - Есть ли независимый от реализации способ преобразования целочисленных типов в битовый шаблон?

Мы всегда можем начинать с нулевого символа и делать на нем достаточно бит операций, чтобы получить его до нужного значения, но я считаю его слишком громоздким. Я также понимаю, что практически все реализации будут использовать представление с двумя дополнениями, но я хочу знать, как это сделать обычным способом. Лично я считаю эту тему довольно интригующей из-за вопроса программирования устройства-драйвера, где весь код, написанный до даты, принимает конкретную реализацию.

4b9b3361

Ответ 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 (для одного дополнения) является представление ловушки или нормальное значение. В случае знака, величины и одного дополнения, если это представление является нормальным значением, оно называется отрицательным нулем.

Ответ 2

Чтобы добавить к mafso отличный ответ, есть часть ANSI C рассуждения, в котором говорится об этом:

Комитет явно ограничил язык C бинарными архитектурами на том основании, что это ограничение было неявным в любом случае:

  • Бит-поля задаются несколькими битами, без упоминания представления "недопустимое целое число". Единственное разумное кодирование для таких битовых полей - двоичное.
  • Целочисленные форматы для printf не предлагают никаких условий для значений "недопустимых целочисленных", подразумевая, что любой результат побитового манипулирования дает целочисленный результат, который может быть напечатан printf.
  • Все методы задания целочисленных констант - десятичные, шестнадцатеричные и восьмеричные - указывают целочисленное значение. Никакой метод, не зависящий от целых чисел, не определен для указания "констант битовой строки". Только двоичное кодирование обеспечивает полное взаимно однозначное сопоставление между битовыми строками и целыми значениями.

Ограничение на двоичные системы нумерации исключает такие раритеты, как код Grey и делает возможные арифметические определения побитовых операторов на неподписанных типах.

Соответствующей частью стандарта может быть эта цитата:

3.1.2.5 Типы

[...]

Тип char, целочисленные типы с подписью и без знака, а также перечисленные типы являются коллективно называемыми интегральными типами. представления интегральных типов должны определять значения с использованием чистого система двоичной нумерации.

Ответ 3

Если вы хотите получить бит-шаблон заданного int, то побитовые операторы - ваши друзья. Если вы хотите преобразовать int в его представление с двумя дополнениями, то ваши арифметические операторы будут вашими друзьями. Эти два представления могут быть разными, поскольку он определяется реализацией:

Std Draft 2011. 6.5/4.. Некоторые операторы (унарный оператор ~ и бинарные операторы <, <, &, & thetas; и &, совместно описываемые как побитовые операторы) должны иметь операнды с целым числом тип. Эти операторы дают значения, зависящие от внутренних представления целых чисел и имеют undefined аспекты для подписанных типов.

Таким образом, это означает, что i<<1 будет эффективно смещать бит-шаблон на одну позицию влево, но что созданное значение может отличаться от i*2 (даже для значений smal i).