Если я приведу указатель от 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;
}
На современных компьютерах все указатели имеют одинаковое представление, обычно это 64-битное или 32-битное слово. Приведение типов указателей не меняет это представление. На самом деле от этого зависит ваш код — printf
с %p
определяется только для указателей void*
, поэтому, когда вы распечатываете указатели, они переинтерпретируются как указатели void*
. Технически это неопределенное поведение (которого лично я бы избегал), но оно вряд ли вызовет проблемы.
Учебное упражнение: увеличьте каждый указатель и снова напечатайте значения обоих указателей. Обратите внимание, что указатель int8_t
увеличился на 1 (байт), а другой указатель int32_t
увеличился на 4 (байта)... Вы должны понимать, что «приведение» — это «директива исходного кода» для компилятора о том, какой исполняемый код оно производит. first
— указатель размером 1 байт, а second
— указатель на первый байт непрерывного блока из 4 байтов. Научитесь различать «исходный код» и «машинный код», который выводит компилятор, следуя директивам при переводе исходного кода.
Если я приведу указатель от
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 См.: stackoverflow.com/q/24867814/20017547
@Halfix: Тот факт, что void *
содержит достаточную информацию для представления любого указателя на тип объекта (K&R не является стандартным C; стандарт C не включает в себя указатели на типы функций), не означает, что он имеет тот же формат или размер, что и другой указатель типы. Другие типы указателей могут содержать меньше информации. Например, int64_t *
может иметь на шесть битов значения меньше, чем void *
.
Вот еще один взгляд на это. В 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 в вашей системе. Ключевой вывод заключается в том, что первый и второй указывают на один и тот же адрес памяти, и напечатанные значения подтверждают это. Ведущие нули — это всего лишь деталь форматирования, специфичная для того, как ваша система представляет адреса указателей.
int8_t*
представляет собой указатель (в наши дни обычно это 64-битный адрес) наint8_t
, а не адрес, представленный какint8_t
.