Когда указатель в C вычисляет объем памяти, на который он ссылается?


Я изучал связанные списки, и рекурсивное определение структуры узла не давало мне покоя

struct node {
    struct node *next;
    int data;
};

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

4 3

4 ответа:

Edit

Сумасшедший. Теперь я понимаю вопрос, который вы на самом деле задаете.

Есть две части к этому. Во-первых, компилятор позволяет создавать указатели на "неполные" типы, размер которых еще не известен. Во-вторых, все указатели на типы struct имеют одинаковый размер и представление, независимо от размера фактического типа struct.

Следуя вашему примеру:

struct node {
    struct node *next;
    int data;
};

Когда компилятор видит объявление для next, Тип struct node является неполный - компилятор еще не знает, насколько большим будет struct node. Однако на данном этапе процесса не требуется знать этот размер, чтобы объявить указатель на этот тип. Вы еще не достигли того момента, когда компилятор должен знать sizeof *next.

Определение типа является полным, когда компилятор видит закрывающее }; определение struct - в этот момент компилятор знает, насколько велик Тип struct node на самом деле.

Оригинал

Компилятор знает размер указываемого типа, поэтому при наличии указателя p выражение p + 1 выдаст адрес следующего объекта указываемого типа.

Дано

int    *ip = 0x1000; // 4 bytes
char   *cp = 0x1000; // 1 byte
double *dp = 0x1000; // 8 bytes

Выражение ip + 1 даст адрес следующего 4-байтового объекта int, или 0x1004, cp + 1 выдаст адрес следующего 1-байтового объекта char, или 0x1001, и dp + 1 выдаст адрес следующего 8-байтового объекта double, или 0x1008.

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

Указатель - это всего лишь одно значение, оно содержит один адрес в памяти.

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

Посмотрите на следующую программу:

struct X {
    char a;
    int  b;
    long c;
};

void y() {
    struct X x;
    x.a = 42;
    x.b = 43;
    x.c = 44;
}

Функция y переводится в следующий ассемблерный код (gcc -s):

y:  
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movb    $42, -16(%rbp)
    movl    $43, -12(%rbp)
    movq    $44, -8(%rbp)
    nop
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
Вы можете ясно видеть значения 42, 43, 44. Компилятор рассчитал смещения полей в a структура x. они относятся к указателю стека (rbp), поскольку значение x выделяется в стеке.

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

Другими словами, это та же причина, по которой вы не можете иметь член этого типа структуры внутри объявления структуры (в этот момент структура не является полной), но может иметь указатель на тип.

Как только вы заставляете элемент указателя указывать на некоторую допустимую память, он также знает начальный адрес. Затем, при разыменовании, он вычисляет адрес смещения и получает значение из этого местоположения, основываясь на ранее упомянутом "типе".

Каждый указатель имеет одинаковый размер в зависимости от вашей системы (4 или 8 байт, насколько я видел).

Поэтому, когда вы вводите такую структуру

struct node {
   struct node *next; // 4 or 8 bytes 
   int data; // 4 or 8 bytes
};                           
Компилятор знает, что это точный размер. Но попробуйте вместо этого пойти этим путем и посмотрите сами.
//Wrong declaration
struct node {
   struct node next; // The compiler cannot decide structure's size
   int data; // 4 or 8 bytes
}; 

Это приведет к ошибке компиляции.