Преобразование короткого замыкания в беззнаковое короткое и сохранение путаницы с битовой комбинацией

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

По разным причинам функция принимает только массив беззнаковых 16-битных целых чисел, поэтому мне нужно хранить подписанные целые числа в беззнаковом 16-битном целочисленном массиве и полностью сохранять тот же битовый шаблон. Я использую gcc (Debian 8.3.0-6) 8.3.0.

unsigned short arr[450];
unsigned short arrIndex = 0;

for (short i = -32768; i < (32767 - 100) ; i = i + 100 )
{
    arr[arrIndex] = i; 

    printf("short value is          : %d\n", i);
    printf("unsigned short value is : %d\n", arr[arrIndex]);
    arrIndex++;
}

Даже несмотря на то, что я говорю printf печатать значения со знаком, я удивлен, увидев, что битовые шаблоны на самом деле отличаются для тех значений, которые меньше нуля. Первые несколько значений приведены ниже:

short value is           : -32768
unsigned short value is  : 32768

short value is           : -32668
unsigned short value is  : 32868

short value is           : -32568
unsigned short value is  : 32968

Что здесь происходит, и как мне сохранить битовый шаблон для значений i ниже нуля?


person Engineer999    schedule 05.05.2020    source источник
comment
Под знаковыми 16-битными целыми числами вы на самом деле имеете в виду 16-битные целые числа со знаком, а ваш int 32-битный, а не 16-битный?   -  person Weather Vane    schedule 05.05.2020
comment
@WeatherVane Вот что я имел в виду, задав спецификатор формата% d .. Я ожидал увидеть отрицательные значения   -  person Engineer999    schedule 05.05.2020
comment
@WeatherVane Если бы я изменил свои комментарии, чтобы сказать шорты вместо целых, стали бы вы счастливее?   -  person Engineer999    schedule 05.05.2020
comment
Вам нужно прояснить, чего ожидает %d и что вы подразумеваете под целыми числами. Наименьший размер int составляет 16 бит. Целочисленные аргументы для вариативной функции, которые меньше, чем исходная int, будут повышены до int в соответствии с обычными целочисленными правилами продвижения. Если ваш int 32 бита, он может обрабатывать весь диапазон signed short и unsigned short.   -  person Weather Vane    schedule 05.05.2020
comment
Вы хотели увеличить arrIndex в конце этого цикла? В противном случае вы получите неверное значение.   -  person templatetypedef    schedule 05.05.2020
comment
Когда я использую% X для первого значения, короткое значение - FFFF8000 шестнадцатеричное, а короткое значение без знака - 8000 шестнадцатеричное. Оба типа имеют одинаковый размер, поэтому меня это немного смущает.   -  person Engineer999    schedule 05.05.2020
comment
... компилятор не регулирует продвижение в соответствии со спецификатором формата. (Он выдает только предупреждающие сообщения о совместимости типов из доброты).   -  person Weather Vane    schedule 05.05.2020
comment
@templatetypedef Вы правы. Опечатка. Исправлено, спасибо   -  person Engineer999    schedule 05.05.2020
comment
%X интерпретирует любые передаваемые вами данные как unsigned, а printf не знает и не заботится о том, как вы определили значение, которое ему было передано. Он видит спецификатор формата во время выполнения и соответствующим образом интерпретирует предоставленные вами байты. Если вы не соответствуете типам, например, передаете double вместо %X, он вслепую смотрит на 4 байта и делает то, что ему говорят.   -  person Weather Vane    schedule 05.05.2020
comment
Ключ состоит в том, чтобы понимать, что биты, хранящиеся в памяти, не изменяются независимо от того, является ли это значение со знаком или без знака того же размера. Рассмотрение битов как со знаком или без знака дает другое представление об одних и тех же битах. Если рассматривать как знаковый, если бит-15 равен 1, значение трактуется как отрицательное (в большинстве систем с двумя компиляциями). Если он рассматривается как неподписанный, особого обращения нет. Так что то, что находится в памяти, не меняется - меняется только то, как вы интерпретируете биты.   -  person David C. Rankin    schedule 05.05.2020
comment
@WeatherVane Re: передайте двойное значение для% X, он слепо смотрит на 4 байта - ›обратите внимание, что передача целочисленных значений и значений FP может включать разные схемы (например, использование регистра FP и стека). 4 байта поиска "%X" могут адресовать недопустимую память. МАК, то есть УБ.   -  person chux - Reinstate Monica    schedule 05.05.2020
comment
@chux хороший момент. Я всегда упускаю из виду различные способы передачи параметров.   -  person Weather Vane    schedule 05.05.2020


Ответы (5)


как мне сохранить битовый шаблон для значений i ниже нуля?

При очень распространенном кодировании с дополнительным кодом до 2 достаточно следующего.

unsigned short us = (unsigned short) some_signed_short;

День BITD с дополнением до единиц и величиной знака, этого было недостаточно, и код будет использовать union из short и unsigned short.

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


битовые комбинации на самом деле различаются для значений меньше нуля.

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


При печати short, unsigned short лучше всего использовать модификатор h printf.

//printf("short value is          : %d\n", i);
//printf("unsigned short value is : %d\n", arr[arrIndex]);
printf("short value is          : %hd\n", i);
printf("unsigned short value is : %hu\n", arr[arrIndex]);
person chux - Reinstate Monica    schedule 05.05.2020
comment
Однако это сбивает с толку. Мое третье значение, которое я распечатал: короткое значение: -32568 беззнаковое короткое значение: 32968. Разве -32568 не превратится в 32568, если при использовании дополнения до 2 отрицательное значение станет таким же положительным? - person Engineer999; 05.05.2020
comment
Нет. При печати short -32568 значение было преобразовано в int и напечатано как -32568. При печати unsigned short 32968 значение было преобразовано в int и напечатано как 32968. Ключевым моментом здесь является печать, поскольку в качестве ... аргументов printf() значение сначала было преобразовано в int, а затем напечатано. В вашей системе диапазон int больше, чем short и unsigned short, поэтому он сохранил исходное значение. - person chux - Reinstate Monica; 05.05.2020
comment
Разве нельзя получить двойное дополнение числа, дать такое же число, но с противоположным знаком? - person Engineer999; 05.05.2020
comment
@ Engineer999 Пожалуйста, подробно объясните, что вы имеете в виду под получением. - person chux - Reinstate Monica; 05.05.2020
comment
@ Engineer999 Когда отрицательное значение short преобразуется в unsigned short, арифметически добавляется USHRT_MAX + 1 (согласно правилам преобразования C) и сумма сохраняется. Побочным эффектом этого является то, что битовая комбинация не изменяется, поскольку знаковый бит в short находится на позиции -32768, а тот же бит в unsigned short - на +32768 разряде. Изменение USHRT_MAX + 1. - person chux - Reinstate Monica; 05.05.2020
comment
@ Engineer999 Думаю, вы захотите прочитать о расширении знаков в дополнении до двух. - person KamilCuk; 05.05.2020
comment
Я понимаю расширение знаков, но, возможно, не понимаю, что вы пытаетесь объяснить. При очень распространенной кодировке дополнения до 2 достаточно следующего: unsigned short us = (unsigned short) some_signed_short. Таким образом, если значение some_signed_short отрицательное, переменная us должна получить положительное значение. итак -3000, конвертируется в 3000? - person Engineer999; 05.05.2020
comment
@ Engineer999 Нет. Значение unsigned short будет negative_signed_short_value + USHRT_MAX + 1 или negative_signed_short_value + 65536, или в данном случае -3000 + 65536 = 62536. Тот же битовый шаблон. Никакого расширения знака не задействовано. - person chux - Reinstate Monica; 05.05.2020

В C, если вы вызываете вариативную функцию и передаете интегральный тип любого вида, язык автоматически продвигает ее до знакового или беззнакового int того же типа. Когда вы затем распечатываете что-то с помощью модификатора %d, в результате вы видите повышенный int.

Например, когда вы звоните

printf("short value is          : %d\n", i);

(Отрицательное) значение i повышается до signed int с тем же значением, поэтому оно распечатывается как отрицательное. Когда вы тогда позвоните

printf("unsigned short value is : %d\n", arr[arrIndex]);

(Беззнаковое) значение arr[arrIndex] повышается до unsigned int, поэтому отображается положительное значение.

Чтобы исправить это, измените свой printf так, чтобы вы указали компилятору отображать результаты конкретно как short переменные:

printf("short value is          : %hd\n", i);
printf("unsigned short value is : %hd\n", arr[arrIndex]);

Теперь вы увидите, что ценности согласуются.

person templatetypedef    schedule 05.05.2020
comment
значение arr [arrIndex] становится беззнаковым int - ›true с 16-битным int/unsigned. Значение arr[arrIndex] повышается до int на 32-битных. - person chux - Reinstate Monica; 05.05.2020

Значения копируются правильно. Посмотрим на следующий код:

#include <stdio.h>

void printit(char *name, short int val)
  {
  printf("%s  %hd  %hu  0x%hX\n", name, val, val, val);
  }

int main()
  {
  short int v1 = 0x8000;
  short int v2 = 0x8064;
  short int v3 = 0x80C8;

  printit("v1", v1);
  printit("v2", v2);
  printit("v3", v3);
  }

Здесь я создал четыре коротких переменных со знаком и установил для них битовые шаблоны. Забудьте на мгновение о «положительном» и «отрицательном» - я просто вставляю небольшой шаблон в эти переменные. В подпрограмме printit эти значения печатаются как десятичное со знаком, десятичное без знака и шестнадцатеричное (чтобы убедиться, что это один и тот же битовый шаблон). Теперь посмотрим на результаты:

v1  -32768  32768  0x8000
v2  -32668  32868  0x8064
v3  -32568  32968  0x80C8

Теперь вы можете видеть, что я просто скопировал использованные вами значения (-32768, -32668 и -32568) и присвоил их переменным. Единственная разница в том, что я сначала преобразовал их в шестнадцатеричный. Тот же битовый шаблон. Те же результаты. Но, за исключением нескольких редких случаев, интерпретация знакового десятичного значения битового шаблона, где десятичное значение является отрицательным, НЕ совпадает с беззнаковой десятичной интерпретацией битового шаблона. Я предлагаю прочитать Дополнение до единицы для двоичных чисел и Дополнение до двух представление отрицательных двоичных чисел.

person Community    schedule 05.05.2020
comment
Боб short int v1 = 0x8000; начинает поведение, определенное внедрением (обычно мягкое), поскольку 0x8000 находится за пределами short диапазона. short int v1 = -32768; или = -0x8000, хотя и менее иллюстративны, не имеют этой проблемы. - person chux - Reinstate Monica; 05.05.2020

Данные копируются правильно, побитно, как вы хотели. Это просто печать, которая отображает его как значение со знаком, потому что arr объявлен как массив значений без знака.

%d выводит данные, переданные как ints (по стандартному определению? Не уверен), что на обычных платформах составляет 4 байта. Аргумент, переданный в printf, перед печатью обновляется до int, что, в зависимости от того, подписан ли рассматриваемый аргумент или нет, потребует расширения знака или нет.

При печати i, которое является значением со знаком, значение будет расширено знаком перед печатью. Например, если i равно -1 (который представлен как 0xFFFF в 2-байтовом значении со знаком с использованием дополнения до двух), то i будет обновлено как int значение 0xFFFFFFFF (которое также равно -1, но представлено четырьмя байтами).

Однако, если i равно -1, тогда при выполнении arr[arrIndex] = i arr[arrIndex] действительно будет установлено в 0xFFFF, и будет копироваться побитно, как вы хотели. Однако, поскольку arr[arrIndex] беззнаковый, в мире беззнакового 0xFFFF представляет 65535. Затем, когда придет время печатать arr[arrIndex], поскольку arr[arrIndex] беззнаковый, значение не будет расширено по знаку, так как это беззнаковое значение. Следовательно, 0xFFFF будет обновлен до 0x0000FFFF, что равно 65535, и напечатан как таковой.

Мы можем проверить это, заставив arr считаться подписанным перед печатью. Таким образом, arr будет обрабатываться так же, как i.

#include <stdio.h>
int main() {
    unsigned short arr[450];
    unsigned short arrIndex = 0;

    for (signed short i = -32768; i < (32767 - 100) ; i = i + 100 )
    {
        arr[arrIndex] = i;
        printf("short value is          : %d\n", i);
        printf("unsigned short value is : %d\n", ((signed short*)arr)[arrIndex]);
        arrIndex++;
    }
}

Выход:

short value is          : -32768
unsigned short value is : -32768
short value is          : -32668
unsigned short value is : -32668
short value is          : -32568
unsigned short value is : -32568
short value is          : -32468
unsigned short value is : -32468
short value is          : -32368
unsigned short value is : -32368
short value is          : -32268
unsigned short value is : -32268
short value is          : -32168
unsigned short value is : -32168

Или мы могли бы напрямую объявить arr как массив значений со знаком для достижения того же результата:

#include <stdio.h>
int main() {
    signed short arr[450];
    unsigned short arrIndex = 0;

    for (signed short i = -32768; i < (32767 - 100) ; i = i + 100 )
    {
        arr[arrIndex] = i;
        printf("short value is          : %d\n", i);
        printf("unsigned short value is : %d\n", arr[arrIndex]);
        arrIndex++;
    }
}
person AnthonyD973    schedule 05.05.2020

Пожалуйста, проверьте for пределы цикла, так как если вы переходите от -32768 к <(32767-100) скачкообразно на 100 значений, вы заполняете элементы массива 655, и вы только объявили 450.

Кроме того, чтобы напечатать значение unsigned short, вам необходимо использовать спецификатор формата %u (или эквивалентный %hu, поскольку shorts преобразуются в int для использования printf()).

Используйте этот пример:

#include <stdio.h>

int main()
{
        short i;
        for (i = -32768; i < (32767 - 100); i += 100) {
                unsigned short j = i;
                printf("Signed  : %d\n", i);
                printf("Unsigned: %u\n", j);
        }

        return 0;
}

Он будет производить:

$ a.out
Signed  : -32768
Unsigned: 32768
Signed  : -32668
Unsigned: 32868
Signed  : -32568
Unsigned: 32968
Signed  : -32468
...
Signed  : -268
Unsigned: 65268
Signed  : -168
Unsigned: 65368
Signed  : -68
Unsigned: 65468
Signed  : 32
Unsigned: 32
Signed  : 132
Unsigned: 132
...
Signed  : 32432
Unsigned: 32432
Signed  : 32532
Unsigned: 32532
Signed  : 32632
Unsigned: 32632
$ _
person Luis Colorado    schedule 06.05.2020