Страницы

Поиск по вопросам

суббота, 14 декабря 2019 г.

Вопрос про указатели

#cpp


Здравствуйте!

Есть в C++ указатели, это область памяти которая содержит адрес, по которому в свою
очередь расположены данные. И меня интересуют следующие вопросы:


Когда мы выделяем память оператором new компилятор создает некую переменную и переводит
указатель на нее? Не совсем понятно, как это выглядит в памяти.
Если у нас есть структура (объект структуры), мы создаем указатель на объект типа
этой структуры, как это всё выглядит в памяти? Создается один указатель (какой у него
будет размер?) или какие-то хитрые манипуляции с указателями на указатели (структура
же может иметь свои поля и методы и может на них, не видимо для программиста, что-то
хитрое провернуто)?

    


Ответы

Ответ 1



1) компилятор вставляет специальный код, который вызывает функцию выделения памяти (malloc к примеру, хотя никто не мешает выделить вначале много памяти, а потом выдавать кусочками). Эта функция возвращает указатель. Его значение присваивается переменной. При необходимости проводит инициализацию (то есть mov). Пример (сильно синтетический! память не освобождается! ничего не инициализируется! только для примера!) int f(int x) { int * c = new int[10]; return c[0]; } получаем такой где то код (для gcc) f(int): sub rsp, 8 mov edi, 40 # это размер 10 * sizeof(int) call operator new[](unsigned long) # собственно выделение памяти mov eax, DWORD PTR [rax] add rsp, 8 ret то есть, программа сама себе выделяет 40 байт памяти. Да, именно сама себе. Компилятор только вставляет вызов специальных функций (которые иногда называют встроенными (builtin) или "магическими" (magick)). Конечно, никто не мешает компилятору самостоятельно зарезервировать память в бинарнике, а вместо выделения просто прописывать указатель на эту часть памяти. В моем синтетическом примере компилятор мог в принципе и 4 байта выделить. 2) в случае структуры компилятор рассчитывает, сколько места нужно под структуру. Обычно оно не меньше, чем выдает sizeof(имя_структуры). Эта память выделяется одним куском. Всем полям присваиваются смещения. struct data { int a; int b; int * d; }; void f(int x) { data * c = new data; c->a = 1; c->b = 2; c->d = 0; } получаем код f(int): mov edi, 16 # это размер структуры push rax call operator new(unsigned long) # выделяем память mov DWORD PTR [rax], 1 # это поле a. оно находиться по нулевому адресу mov DWORD PTR [rax+4], 2 # это поле b mov QWORD PTR [rax+8], 0 # а указатель занимает 8 байт, поэтому размер структуры 16. pop rdx ret До этого момента от классического C ничего не отличается. Но в С++ структуры могут иметь конструктор. В этом случае компилятор вставит его вызов. В том случае, если структура содержит внутри себя другие структуры, то создается комбинация. И снова пример: struct temp { int c; int d; }; struct data { int a; int b; temp f; }; void f(int x) { data * c = new data; c->a = 7; c->b = 2; c->f.d =5; } и его код f(int): push rax mov edi, 16 call operator new(unsigned long) mov DWORD PTR [rax], 7 mov DWORD PTR [rax+4], 2 mov DWORD PTR [rax+12], 5 pop rdx ret Я специально выбрал различные значения, что бы можно было найти соответствия. Как видно, компилятор просто вставил структуру и на уровне кода теперь формально есть только одна "структура": struct fake { int a; int b; int c; int d; }; Остался только вопрос с функциями, которые могут иметь структуры. Здесь все просто. Обычно компилятор добавляет неявный параметр к таким функциям, в котором передает указатель на структуру. Саму структуру никак модифицировать не нужно - для них ведь нет полиморфизма - в любом случае тип структуры будет известный и можно вставить правильный вызов.

Ответ 2



@Alerr, Вы правы, указатель это область памяти в которую записан адрес. Размер этой памяти зависит от архитектуры компьютера и ОС. Если немного сдвинуться в конкретику, то в C/C++ Linux размер указателя == sizeof(long). Для 32-bit x86 это 4 байта, а для 64-bit x86 - 8 байт. Вообще говоря, в этой памяти всегда есть какие-то биты (может все нули, а может все единички...), поэтому фактически указатель всегда на что-то указывает, хотя текущее значение может быть некорректным (даже не принадлежащим адресному пространству процесса). Обычно про такой указатель говорят, что он не инициализирован. Остальное (по пунктам 1, 2 Вам уже замечательно описал @KoVadim).

Комментариев нет:

Отправить комментарий