Неожиданное значение при разыменовании указателя в C

Следующий код

#include <stdio.h>

int main()
{
    long long data = 0xFFFEABCD11112345;

    char *pData = (char *)&data;
    printf("Value at address %p is %x\n", pData, *pData);

    pData = pData + 5;
    printf("Value at address %p is %x\n", pData, *pData);

    return 0;
}

выдает результат, аналогичный

Value at address 00000023515FFC00 is 45
Value at address 00000023515FFC05 is ffffffab

Учитывая, что pData — это char *, я ожидал, что второе значение будет ab вместо ffffffab. Я считаю, что виноват спецификатор формата %x, но я не до конца его понимаю. Откуда берутся ведущие f?

Добро пожаловать в СО. Если вы хотите увидеть ab, используйте unsigned char. В вашей системе char — это подписанный тип данных, знак которого расширяется до int, когда вы передаете его printf. Тип char повышается до int при передаче функции без прототипа или функции с переменным числом аргументов.

Gerhardh 16.04.2024 11:52
char — это подписанный тип данных на вашем компьютере. Следовательно, 0xab — отрицательное число. При преобразовании в int он становится 0xffffffab. Вместо этого используйте unsiged char.
Tom Karzes 16.04.2024 11:53

Должен быть обман....

Gerhardh 16.04.2024 11:56

Проблема в том, что char имеет подпись, определяемую реализацией, см. связанный дубликат. В случае, если он подписан, расширение знака происходит всякий раз, когда этот char продвигается, поскольку 0xAB становится отрицательным представлением, когда char подписан. Куча ffff возникла из-за продвижения аргумента printf по умолчанию с (подписанного) char на int. Продвижение аргумента по умолчанию глупо и ничего не знает о спецификаторе формата %x, ожидающем беззнаковый параметр. (unsigned char)*pData дало бы ожидаемый результат, так как в ходе акции продление знака не происходит.

Lundin 16.04.2024 12:00

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

Lundin 16.04.2024 12:04

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

atta 16.04.2024 12:07

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

Lundin 16.04.2024 12:09

@Gerhardh Как насчет этого? stackoverflow.com/questions/3555791/…

Andrew Henle 16.04.2024 13:05
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

char может быть подписанным или беззнаковым в зависимости от компилятора. Символ по умолчанию подписан или не подписан? В данном случае он выглядит подписанным.

На обычных компьютерах знак char может содержать только значения от -128 до 127. 0xAB на таком компьютере будет представлением дополнения до 2 десятичного отрицательного числа -85.

В C есть различные формы неявного повышения типов, которые происходят, когда в большинстве выражений используются небольшие типы, такие как char, или, как в этом случае, при передаче вариативной функции printf. Специальный набор неявных правил повышения для функций с переменным числом аргументов называется «повышением аргумента по умолчанию» и утверждает, что небольшие целочисленные типы повышаются до int независимо от знака.

Если у нас есть знак char со значением -85, то при повышении до int этот знак учитывается, что известно как расширение знака. Это означает, что значение по-прежнему равно -85, но двоичное представление расширенного int может быть 0xFFFFFFAB (при условии, что 32-битное целое число).

Однако если бы у нас был беззнаковый char со значением 0xAB/171, то при повышении до int значение просто сохраняется и никакого знака нет. Таким образом, мы могли бы избежать расширения знака, приведя: (unsigned char)*pData. Явное преобразование знака в беззнак четко определено.

Строка формата printf не имеет отношения к этой рекламной акции. %x ожидает параметр, который равен unsigned int, поэтому мы по сути лжем при печати, поскольку передаем char, повышенный до int, строго говоря, неопределенное поведение. Однако printf в этом случае просто считывает двоичное представление int и представляет его как 0xFFFFFFAB.

Еда на вынос:

  • Использование char (или знаковых типов в целом) при работе с необработанными двоичными данными или аппаратном программировании — плохая идея. Используйте unsigned char или uint8_t.
  • Вариативные функции небезопасны по типу и имеют особый набор странных правил. Избегайте их, когда можете.

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