Почему изменение типа указателя путем приведения не меняет результирующее значение указателя?

Если я приведу указатель от int32_t к int8_t, почему значение указателя не изменится? Я ожидал, что полученное значение изменится с 32-битного адреса на 8-битный адрес.

При экспериментировании с приведенным ниже кодом он не меняется. Почему?

#include <stdio.h>
#include <stdint.h>

int main()
{
    int32_t * first = (int32_t *) 0xFFFFFFFF;
    int8_t * second = (int8_t *) first;

    printf("Gives: %p \n", first);
    printf("Gives: %p \n", second);

    return 0;
}
int8_t* представляет собой указатель (в наши дни обычно это 64-битный адрес) на int8_t, а не адрес, представленный как int8_t.
Paul Hankin 13.07.2024 05:50

На современных компьютерах все указатели имеют одинаковое представление, обычно это 64-битное или 32-битное слово. Приведение типов указателей не меняет это представление. На самом деле от этого зависит ваш код — printf с %p определяется только для указателей void*, поэтому, когда вы распечатываете указатели, они переинтерпретируются как указатели void*. Технически это неопределенное поведение (которого лично я бы избегал), но оно вряд ли вызовет проблемы.

Paul Hankin 13.07.2024 05:56

Учебное упражнение: увеличьте каждый указатель и снова напечатайте значения обоих указателей. Обратите внимание, что указатель int8_t увеличился на 1 (байт), а другой указатель int32_t увеличился на 4 (байта)... Вы должны понимать, что «приведение» — это «директива исходного кода» для компилятора о том, какой исполняемый код оно производит. first — указатель размером 1 байт, а second — указатель на первый байт непрерывного блока из 4 байтов. Научитесь различать «исходный код» и «машинный код», который выводит компилятор, следуя директивам при переводе исходного кода.

Fe2O3 13.07.2024 08:31
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать 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
3
92
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

Если я приведу указатель от int32_t к int8_t, почему значение указателя не изменится? Я ожидал, что полученное значение изменится с 32-битного адреса на 8-битный адрес.

Оба first и second являются указателями. first указывает на объект int8_t, а second указывает на объект int32_t.

Цифра 8 в int8_t * обозначает количество битов в объекте, на который указывает, а не в указателе. 32 в int32_t * обозначает количество битов в объекте, на который указывает, а не в указателе.


Примечание: %p ожидает void *, а не uint8_t * или uint32_t *. Если аргумент не соответствует соответствующему спецификатору формата, поведение не определено.

Второе издание K&R (на моей книжной полке с середины 80-х годов) в главе 5.11 прямо утверждает, что void* можно преобразовать в указатель любого другого типа и наоборот без потери информации. Таким образом, предоставление других типов указателей в качестве аргумента для %p (даже без явного приведения) не является UB в C.

Halfix 13.07.2024 08:49

@Halfix См.: stackoverflow.com/q/24867814/20017547

Harith 13.07.2024 08:59

@Halfix: Тот факт, что void * содержит достаточную информацию для представления любого указателя на тип объекта (K&R не является стандартным C; стандарт C не включает в себя указатели на типы функций), не означает, что он имеет тот же формат или размер, что и другой указатель типы. Другие типы указателей могут содержать меньше информации. Например, int64_t * может иметь на шесть битов значения меньше, чем void *.

Eric Postpischil 13.07.2024 13:26

Вот еще один взгляд на это. В C union позволяет объявить одну и ту же ячейку памяти с разными типами (часто полезно для протоколов, где формат зависит от отправляемых данных). Упрощенный пример выглядит так:

union test_u {
    uint32_t first;
    uint8_t second;
};

int main(void) {
    union test_u u;
    u.first = 0xffffffff;  // Set all bits to 1
    u.second = 0;          // Sets 8 bits to 0
    printf("%x\n", u.first);  
}

Результат зависит от процессора, но это будет либо ffffff (верхние 8 бит равны 0), либо ffffff00 (нижние 8 бит). Вы можете назначить указатели на объединение:

int main(void) {
    union test_u u;
    uint32_t *first_p = &u.first;
    uint8_t *second_p = &u.second;

    *first_p = 0xffffffff;  // Set all bits to 1
    *second_p = 0;          // Sets 8 bits to 0
    printf("%x\n", u.first);  
}

Это делает то же самое, только через два указателя.

Поскольку объединение содержит только одно значение, доступное как два типа, вы можете сделать то же самое с приведением:

int main(void) {
    int32_t test_int;
    uint32_t *first_p = (*uint32_t)(&test_int);
    uint8_t *second_p = (*uint8_t)(&test_int);

    *first_p = 0xffffffff;  // Set all bits to 1
    *second_p = 0;          // Sets 8 bits to 0
    printf("%x\n", int32_t);  
}

Обратите внимание, что test_int — это 32 бита, но со знаком. Подписан он или нет, не имеет значения, поскольку к нему не будет прямого доступа, имеет значение только размер. В данном случае &test_int явно приводится к совпадению first_p и second_p, но раньше в этом не было необходимости, поскольку можно было выбрать опцию union с типом соответствия.

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

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