C/C++. Преобразование типов указателей

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

#include <stdio.h>

int main() {
   
    int *p1;
    double *p2 = (double*)p1;
   
    printf("%p\n", p1); // 7FFD7000
    printf("%p\n", p2); // 7FFD7000
   
    return 0;


}


Для указателей разного типов эта область памяти по правилам интерпретации указателей рассматривается как заполненная переменными либо одного, либо другого типа. Область памяти (захват) может иметь различную структуру (байтовую, словную и др.) в зависимости от того, через какой указатель мы с ней работаем (имеется в виду тип данных указателя).
Присваивание значения указателя одного типа указателю другого типа сопровождается действием, которое называется в С преобразованием типа указателя, и которое в С++ обозначается всегда явно. На самом деле это действие является чистой фикцией (команды транслятором не генерируются). Транслятор просто запоминает, что тип указываемой переменной изменился, поэтому операции адресной арифметики и косвенного обращения нужно выполнять с учетом нового типа указателя.
Рассмотрим следующий пример. Проинициализируем переменную x типа short int и определим для нее указатель p. Напомним, что размер памяти выделяемый под переменные данного типа занимает sizeof(short int) = 2 байта. То есть 16 битов - 16 разрядов. Далее создадим указатель q, к которому приводится указатель p. Для типа данных int отводится sizeof(int) = 4 байта. То есть 32 бита - 32 разряда.

#include <stdio.h>

int main() {
   
    short int x = 0;
   
    short int *p = &x;
    int *q = (int*)p;
   
    // short int pointer address = 0022FF26
    printf("short int pointer address = %p\n", p);
    // int pointer address = 0022FF26
    printf("int pointer address = %p\n", q);
   
    // 0
    printf("%d\n", *p);
    // -152 (garbage)
    printf("%d\n", *(++p));   
    // -9961472
    printf("%d\n", *q);
   
    return 0;
}


Как было сказано выше, в указатель q копируется адрес указателя p (что подтверждается выводом их адресов), так как у них разные типы, то адресная арифметика и косвенное обращение к памяти будет разным, поэтому каждый из них будет захватывать определенную их типом область памяти, то есть иметь разную структуру памяти. На примере видно, как ведут себя оба указателя: при разыменовании указатель p дает значение переменной x, это очевидно, указатель же q дает неожиданный результат -9961472, это связано с тем, что в область захвата памяти вошла неинициализированная область памяти, в котором лежит мусор, в примере мы обратились к этому мусору - там лежит значение  -152. Стоит отметить, что в вашем случае у вас могут быть получены свои адреса и значения. Если же теперь представить полученные значения в бинарном виде и обратить внимание на типы данных указателей, то можно "увидеть" структуру рабочей области памяти, которая изображена на следующем рисунке.


Как известно функции динамического распределения (malloc, calloc, realloc) возвращают указатель типа void* на распределенную динамически память. Указатели данного типа могут быть приведены к абсолютно любому типу, но операция по извлечению данных по адресу запрещены. Из вышеприведенного ясно, что в этом случае мы столкнемся с неопределенностью в области захвата памяти!
Модифицируем вышеприведенный пример так, что бы по второму указателю при разыменовании было исключено взятие мусора

#include <stdio.h>

int main() {
   
    short int x[2] = {0, 0};
   
    short int *p = x;
    int *q = (int*)p;
   
    // short int pointer address = 0022FF24
    printf("short int pointer address = %p\n", p);
    // int pointer address = 0022FF24
    printf("int pointer address = %p\n", q);
   
    // 0
    printf("%d\n", *p);
    // 0
    printf("%d\n", *q);
   
    return 0;


}


За счет использования массива, мы смогли проинициализировать рабочую область памяти. Как итог, снова и снова убеждаемся в необходимости соблюдать аккуратность и внимательность при работе с указателями. Всем удачи в нашем нелегком ремесле :)