Странное поведение в C, когда шестнадцатеричное значение (более 0x7f) присваивается (подписанному) символу

Когда шестнадцатеричное значение «0x000000a1» присвоено (со знаком) char, char равен «0xffffffa1». Кто-нибудь может объяснить это странное поведение?

void main(){
    char testVar = 0x000000a1;

    printf("%x \n",testVar); //prints ffffffa1
    printf("%d \n",testVar); //prints -95

}

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

void main(){
    unsigned char testVar = 0x000000a1;

    printf("%x \n",testVar); //prints a1
    printf("%d \n",testVar); //prints 161

}

также работает должным образом, когда вы назначаете значение в пределах ограничения ASCII 127 (или 0x7f)

void main(){
    unsigned char testVar = 0x7f;

    printf("%x \n",testVar); //prints 7f
    printf("%d \n",testVar); //prints 127

}

что я понимаю теперь, задав вопрос:

  • Вы не можете присвоить 0x000000a1 символу, поскольку длина символа составляет всего 1 байт. Вы назначаете только часть a1.
  • Первый бит знакового значения указывает, является ли оно отрицательным или нет. (1000 0000 отрицательно и равно -128. 0111111 не отрицательно и равно 127)
  • По какой-то причине printf необходимо расширить переменную char до int, прежде чем она выведет значение.
  • когда printf преобразует знак char в int, он распознает, когда char отрицательный, и, следовательно, сохраняет отрицательное значение, расширяя его 1 вместо 0. (1000 0000b(-128dez) расширяется до 1111 1111... 1111 1111 1000 0000b(все еще -128dez))

У 0xA1 установлен старший бит, поэтому, когда (со знаком) значение char преобразуется в int при передаче в printf(), оно расширяется по знаку и печатается как отрицательное число (-95). Это нормально и вполне ожидаемо. Значения 0x00..0x7F являются положительными, а 0x80..0xFF отрицательны (со знаком) char.

Jonathan Leffler 03.06.2024 03:34

Обратите внимание, что void main() действует только в Windows (поскольку в документах Microsoft это допустимо). Везде main() возвращается int. См. Что должна возвращать функция main() в C и C++? Кроме того, печатать пробел перед новой строкой не рекомендуется. Конечные пробелы должны быть для вас анафемой как в исходном коде, так и в результатах.

Jonathan Leffler 03.06.2024 03:36
char может быть подписанным или беззнаковым и по умолчанию на вашей платформе это 8-битный знаковый тип. Если он подписан, то, очевидно, 0xa1 выходит за пределы диапазона, поэтому он не может иметь значение 161. Большинство компиляторов имеют переключатель для переключения между знаковыми и беззнаковыми символами.
phuclv 03.06.2024 03:48

ОК, я думаю, теперь я понял, поправьте меня, если я ошибаюсь: первый бит переменной char является флагом, если значение отрицательное (1000 0000 будет отрицательным значением), теперь, когда char расширяется до подписанного int для оператор printf расширяет символ в зависимости от того, является ли символ отрицательным или нет. Я предполагаю, что когда вы явно указываете, что char не имеет знака, printf больше не распознает первый бит как флаг отрицательного значения и поэтому расширяет переменную нулями.

bangingmyheadontable 03.06.2024 04:03

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

phuclv 03.06.2024 05:35
char всегда имеет длину один байт, в противном случае ваше понимание верно.
n. m. could be an AI 03.06.2024 06:45

Недавно кто-то задал аналогичный вопрос: char * vs unsigned char *

Lundin 03.06.2024 08:43
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
84
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы не можете присвоить 0x000000a1 символу, поскольку длина символа составляет всего 2 байта. Вы назначаете только часть a1.

char по определению всегда имеет размер 1 байт. (Однако в экзотических системах он может иметь более 8 бит.) Ключевым моментом здесь является то, что char имеет подпись, определяемую реализацией: он может быть как подписанным, так и беззнаковым, в зависимости от компилятора. Символ по умолчанию подписан или не подписан? Судя по всему в вашем случае это подписано.

Количество нулей перед значением не имеет ни малейшего значения и не влияет на тип. Все, что они делают, это обманывают программиста, думая, что они имеют какой-то смысл.

Первый бит знакового значения указывает, является ли оно отрицательным или нет (1000 0000 является отрицательным и равно -128. 0111111 не является отрицательным и равно 127).

Да, если это MSB.

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

или по какой-то причине printf необходимо расширить переменную char до int, прежде чем она выведет значение.

-95 — это вариативная функция (переменное количество аргументов), и для этих функций существует специальное правило неявного повышения типа, называемое «продвижение аргументов по умолчанию». При передаче небольшого целочисленного типа, такого как printf или char, в вариативную функцию, он всегда неявно преобразуется в short. (И при передаче int он будет повышен до float.)

когда printf преобразует знаковый символ в int, он распознает, когда символ отрицательный, и, следовательно, сохраняет отрицательное значение, расширяя его с помощью 1 вместо 0.

Правильный. Во время этой акции, если ваш исходный тип был подписан и имеет отрицательное значение, это учитывается и знак сохраняется, так называемое «расширение знака». Таким образом, в двоичном представлении вещей вместо (со знаком) double 0xa1 = -95 вы получаете char 0xffffffa1 = все еще -95.

Затем вы лжете printf, сообщив ему, что он ожидает параметр int. Строго говоря, это неопределенное поведение, но любой разумный компилятор преобразует %x в unsigned int, и это четко определенное преобразование, в результате чего у вас останется 0xffffffa1, но теперь это беззнаковое представление, поэтому оно равно десятичному значению 4294967201.

Другие вопросы по теме