Переменная и указатель

Рейтинг: 2Ответов: 3Опубликовано: 12.05.2023

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

Я знаю, что переменная занимает место в памяти, в зависимости от типа:

  • int - 4байта

  • char - 1байт

  • ...

  • void* - 8байт(как и любой указатель)

Переменные хранят в себе значения. Указатели так же, но значением является адрес другой переменной.

А не понимаю я вот что: эти 4 байта для int(к примеру) физически лежат в памяти. Если мы хотим получить доступ к ячейкам памяти, то нам все же где-то нужно хранить указатель на них!? Получается любая переменная по факту занимает 8 байт и разделение на простая и указатель переменная есть ни что иное как синтаксический сахр?

Ответы

▲ 3Принят

Если мы хотим получить доступ к ячейкам памяти, то нам все же где-то нужно хранить указатель

Да.

разделение на простая и указатель переменная есть ни что иное как синтаксический сахр?

Нет.

Есть два основных вида переменных:

  • Глобальные и статические — их адреса известны во время линковки (или задаются один раз при запуске приложения), и в каком-то виде зашиваются прямо в код, который к ним обращается.

  • На стеке — в одном из регистров обычно хранится указатель на верхушку стека, а к переменным обращаются по смещению относительно этого указателя. Смещение зашивается в код, который к ним обращается.

    Либо такие переменные могут быть соптимизированы и храниться в регистрах, тогда в код зашивается, какой именно это регистр.

Объекты в куче не являются переменными, потому что у них нет имен (грубо говоря). И для них нет какого-то автоматического вычисления адреса. К ним можно обратиться только через указатель вручную (так же, как можно обратиться к любому другому объекту).


разделение на простая и указатель переменная

Создается ощущение, что вы думаете, что указатели отдельно, а все остальные объекты отдельно.

На самом деле, указатели - это подвид объектов, и на них действуют все те же правила, что и на остальные объекты.

▲ 1

Когда мы создаем в программе переменную, например, типа int, мы резервируем в статической памяти 4 байта для ее хранения. Компилятор на этапе компиляции уже знает адрес, где искать эту переменную, поэтому больше 4 байт ему не потребуется. Но при создании переменных в куче, с помощью динамического выделения памяти (malloc и прочего), без дополнительной информации о местоположении уже не обойтись, ведь на этапе компиляции мы никак не можем знать, какое место в памяти нам подсунет система.

▲ 1

Есть три основных типа хранилища (storage class): стек (локальное), куча (динамическое) и статическое, а также ещё особняком есть память для кода:

  • Если переменная хранится в куче, то указатель должен быть, без него никак, иначе будет утечка памяти.

  • Если хранится в статической памяти, то память для неё резервируется на этапе компиляции и выделяется до запуска main.

  • Если переменная стековая, то тут уже зависит от оптимизаций. Раньше даже было ключевое слово register. У регистровых переменных нельзя было брать адрес даже при условии, что это не требование, а совет компилятору.

  • Код тоже хранится в памяти. Можно брать адрес функций.

Собственно, когда вы пишете имя переменной, то вы обращаетесь к какому-то адресу, т.к. имена переменных - это абстракция. Поэтому всё имеет адрес: либо в RAM (на самом деле в виртуальной памяти), либо где-то в кэше процессора, или даже в регистрах. Но получить адрес вы можете только для объектов в RAM.

Любая переменная не занимает 8 байт. Адрес любого объекта занимает место в памяти. Кроме того, 8 байт актуальны только для 64-битных систем, и то не во всех случаях:

#include <iostream>

struct A {
    void method() {}
    int data{37};
};

int main() {
    A a;
    std::cout << sizeof(&a) << std::endl;
    std::cout << sizeof(&A::data) << std::endl;
    std::cout << sizeof(&A::method) << std::endl;
    return 0;
}

Предполагаемый вывод:

8
16
16