Стандарт C на отрицательном нуле (1-е дополнение и знаковая величина) - PullRequest
7 голосов
/ 28 октября 2011

Все эти функции дают ожидаемый результат на моей машине.Все ли они работают на других платформах?

В частности, если x имеет битовое представление 0xffffffff на машинах дополнения 1 или 0x80000000 на машинах со звездной величиной, что стандарт говорит о представлении (без знака) x?

Кроме того, я думаю, что (без знака) приведение в v2, v2a, v3, v4 является избыточным.Это правильно?

Предположим, sizeof (int) = 4 и CHAR_BIT = 8

int logicalrightshift_v1 (int x, int n) {

    return (unsigned)x >> n;
}

int logicalrightshift_v2 (int x, int n) {

    int msb = 0x4000000 << 1;
    return ((x & 0x7fffffff) >> n) | (x & msb ? (unsigned)0x80000000 >> n : 0);
}

int logicalrightshift_v2a (int x, int n) {

    return ((x & 0x7fffffff) >> n) | (x & (unsigned)0x80000000 ? (unsigned)0x80000000 >> n : 0);
}

int logicalrightshift_v3 (int x, int n) {

    return ((x & 0x7fffffff) >> n) | (x < 0 ? (unsigned)0x80000000 >> n : 0);
}

int logicalrightshift_v4 (int x, int n) {

    return ((x & 0x7fffffff) >> n) | (((unsigned)x & 0x80000000) >> n);
}

int logicalrightshift_v5 (int x, int n) {

    unsigned y;
    *(int *)&y = x;
    y >>= n;
    *(unsigned *)&x = y;
    return x;
}

int logicalrightshift_v6 (int x, int n) {

    unsigned y;
    memcpy (&y, &x, sizeof (x));
    y >>= n;
    memcpy (&x, &y, sizeof (x));
    return x;
}

Ответы [ 2 ]

8 голосов
/ 28 октября 2011

Если x имеет битовое представление 0xffffffff на 1 дополняют машины или 0x80000000 на машинах знаковых величин, что стандарт говорит о представлении (без знака) x?

Преобразование в unsigned определяется в терминах значений , а не представлений. Если вы конвертируете -1 в unsigned, вы всегда получаете UINT_MAX (поэтому, если ваш unsigned равен 32 битам, вы всегда получаете 4294967295). Это происходит независимо от представления чисел со знаком, которые использует ваша реализация.

Аналогично, если вы конвертируете -0 в unsigned, тогда вы всегда получаете 0. -0 численно равно 0.

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

Пройдя все функции по очереди:

int logicalrightshift_v1(int x, int n)
{
    return (unsigned)x >> n;
}

Результат этой функции для отрицательных значений x будет зависеть от UINT_MAX и будет дополнительно определяться реализацией, если (unsigned)x >> n не находится в диапазоне int. Например, logicalrightshift_v1(-1, 1) вернет значение UINT_MAX / 2 независимо от того, какое представление машина использует для чисел со знаком.

int logicalrightshift_v2(int x, int n)
{
    int msb = 0x4000000 << 1;
    return ((x & 0x7fffffff) >> n) | (x & msb ? (unsigned)0x80000000 >> n : 0);
}

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

Типы 0x7fffffff и 0x80000000 будут зависеть от диапазонов различных типов, что будет влиять на продвижение других значений в этом выражении.

int logicalrightshift_v2a(int x, int n)
{
    return ((x & 0x7fffffff) >> n) | (x & (unsigned)0x80000000 ? (unsigned)0x80000000 >> n : 0);
}

Если вы создаете значение unsigned, которое не находится в диапазоне int (например, с учетом 32-битного int, значения> 0x7fffffff), тогда неявное преобразование в операторе возврата создает реализацию - определенное значение. То же самое относится к v3 и v4.

int logicalrightshift_v5(int x, int n)
{
    unsigned y;
    *(int *)&y = x;
    y >>= n;
    *(unsigned *)&x = y;
    return x;
}

Это все еще определяется реализацией, потому что не определено, соответствует ли бит знака в представлении int биту значения или биту заполнения в представлении unsigned. Если он соответствует биту заполнения, это может быть представление прерывания, и в этом случае поведение не определено.

int logicalrightshift_v6(int x, int n)
{
    unsigned y;
    memcpy (&y, &x, sizeof (x));
    y >>= n;
    memcpy (&x, &y, sizeof (x));
    return x;
}

К нему применимы те же комментарии, что и к v5.

Кроме того, я думаю, что (без знака) приведение в v2, v2a, v3, v4 является избыточным. Является это правильно?

Это зависит. В качестве шестнадцатеричной константы 0x80000000 будет иметь тип int, если это значение находится в диапазоне int; в противном случае unsigned, если это значение находится в диапазоне unsigned; в противном случае long, если это значение находится в диапазоне long; в противном случае unsigned long (поскольку это значение находится в пределах минимально допустимого диапазона unsigned long).

Если вы хотите убедиться, что он имеет тип без знака, добавьте константу к U, к 0x80000000U.


Резюме:

  1. Преобразование числа, большего чем INT_MAX в int, дает результат, определенный реализацией (или, на самом деле, позволяет повысить сигнал, определяемый реализацией).

  2. Преобразование числа вне диапазона в unsigned выполняется путем повторного сложения или вычитания UINT_MAX + 1, что означает, что это зависит от математического значения , а не от представления.

  3. Проверка отрицательного int представления как unsigned не переносима (хотя положительные int представления в порядке).

  4. Генерация отрицательного нуля с помощью битовых операторов и попытка использовать результирующее значение не переносимы.

Если вы хотите «логические сдвиги», тогда вы должны везде использовать беззнаковые типы. Типы со знаком предназначены для работы с алгоритмами, в которых значение имеет значение, а не представление.

2 голосов
/ 28 октября 2011

Если вы следуете стандарту слову, ни одно из них не будет одинаковым на всех платформах.

В v5, вы нарушаете строгое псевдонимы, что является неопределенным поведением.

В v2 - v4, вы подписали сдвиг вправо, который определяется реализацией.(подробности см. в комментариях)

В v1 вы подписали подпись без знака, что является реализацией, определенной, когда число выходит за пределы диапазона.

РЕДАКТИРОВАТЬ:

v6 может на самом деле работать, учитывая следующие предположения:

  • 'int' - это дополнение 2 или 1.
  • unsigned и int имеют одинаковый размер (как в байтах, так и в битах и ​​плотно упакованы).
  • Порядковый номер unsigned совпадает с int.
  • Заполнение и битовыймакет такой же: (См. комментарий в кафе для более подробной информации.)
...