Как malloc узнает, сколько места в памяти обрабатывается для индекса?

#c #malloc

Вопрос:

Когда мы используем malloc , он возвращает указатель на начало адреса памяти фиксированного размера, который был передан в malloc. Например, malloc(40) бросит мне какой-нибудь неинициализированный фрагмент памяти длиной 40 байт. Дело в том, что я видел примеры кода, в котором люди индексируют этот фрагмент памяти. Мой вопрос в том, как malloc определить размер индекса?

Например, возьмем этот фрагмент кода,

 #include <stdio.h>
#include <stdlib.h>

int main()
{   
    char **array;
    array = malloc(3 * sizeof(char *));

    for (int i=0; i < 3; i  ) {
        array[i] = malloc(10);
    }
    
    for (int i=0; i<10; i  ) {
        free(array[i]);
    }
    free(array);
    return 0;
}
 

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

char **array создает переменную «массив», где она станет указателем на указатель на символ. Это означает, что если мы разыменуем это значение, оно даст нам адрес памяти, в котором char оно хранится.

array = malloc(3 * sizeof(char *)) . Давайте предположим, что здесь sizeof(char *) всегда будет возвращено 8. Продолжая, это создаст неинициализированный фрагмент памяти длиной 32 байта. Ключевым моментом здесь является то, что он имеет длину 32 байта, как он обрабатывает индексируемый размер?

array[i] = malloc(10) это часть моего замешательства здесь. У нас есть неинициализированный фрагмент памяти длиной 32 байта, как мы его индексируем?

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

       0x02                     0x0A                 0x12
[     0x90             |       0x91           |     0x92           ]
 <-- sizeof(char*)  ->  <-- sizeof(char*)  ->  <-- sizeof(char*)  ->
       ^
       |  
       |
    0x01 (memory address of variable array) (array - points to 0x02)

-- Random memory locations

0x90 -- | Starting from the memory address location 0x90, the next sizeof(char) bytes will representing the value in this memory address location.
['c']

0x91
['a']

0x92
['t']
 

Насколько я понимаю, мэллок будет знать индексируемый размер по приведению, которое мы сделали для нашего начального указателя, т. Е. char* внутри char** array . Это означает, что наш указатель, возвращенный из malloc(40) , будет указывать в данном примере на адресное пространство памяти, расположенное в 0x02 (начале массива).

Каждый раз, когда мы выполняем действие array[i] , которое мы на самом деле выполняем 0x02 sizeof(char*) * i , оно будет перемещать указатель в начало нового местоположения. Это означает, например, что, когда мы это делаем array[1] , мы на самом деле делаем 0x02 sizeof(char*) * 1 то, что подтолкнуло бы нас к 0x02 8 (0x0A) этому . Это означает, что из расположения адреса памяти 0x0A следующие sizeof(char *) байты будут считываться как индекс, хранящийся в этом месте в памяти. В этом примере это будет a char * , в моем примере я написал 0x90 , что означает , что в другом месте в памяти 0x90 следующее sizeof(char) , т. е. 1 байт будет иметь фактическое значение. Фактическое значение, представляющее » c » (например), но это может быть расположено где-то еще в памяти, не связанное с malloc.

Используя эту формулу, мы можем, например, получить массив целых чисел, возвращенный из malloc, имея int* ten_int_array = malloc(10 *sizeof(10)) . Теперь формула будет скорректирована ten_int_array sizeof(int) * i . Что сделало бы malloc не индексируемым с фиксированным размером.

Спасибо за любые ответы, я пытаюсь проверить свои предположения здесь.

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

1. Это не имеет к этому никакого отношения malloc . Это не malloc то, что делает индексацию. Это компилятор. Когда код выполняет индексацию, компилятор генерирует правильные смещения в зависимости от типа указателя. Все malloc , что нужно, — это выделить тот объем памяти, который вы указываете (ну, возможно, немного больше для ведения учета и выравнивания). Так что не совсем понятно, в чем заключается ваш вопрос.

2. когда у вас есть a T * и вы индексируете его, вы получаете доступ к ячейкам памяти, как если бы они содержали массив смежных элементов T .

3. Размер указателя не имеет значения. Указатель — это просто адрес памяти. Важно то, что делает компилятор, когда он вычисляет смещение в массиве. Это делается путем умножения индекса массива на размер элемента массива. Полученное число представляет собой смещение (в байтах) от адреса памяти, хранящегося в указателе на индексированный элемент в массиве.

4. Все, что делает malloc, — это выделяет блок памяти запрошенного вами размера, а затем возвращает адрес памяти в первый байт этого блока.

5. Читая эти комментарии, если бы я поместил это в формулу, это было бы что-то вроде, T* example = malloc(10 * sizeof(T)) , то T[i] , следовательно, на самом деле было бы, example sizeof(T) * i .

Ответ №1:

Вот что происходит. Предположительно, вы запускаете его в 64-разрядной системе, где для адреса памяти требуется 8 байт (64 бита).

char *str; объявляет переменную указателя, обозначаемую, * которая может указывать на место в памяти. По вышеупомянутому соглашению он должен быть размером 8 байт. Компилятор знает, что объект, на который он должен указывать, является a char . Он абсолютно не знает, должны ли быть другие символы, которые следуют за этим местоположением или предшествуют ему, только программист знает.

Таким образом, str = malloc(10); выделяется достаточно места в памяти для хранения 10 символов, включая окончание «0». Адрес памяти присваивается переменной указателя str .

char **array; объявляет указатель * на указатель * . Это еще одна 8-байтовая переменная, которая, как известно компилятору, указывает на другой указатель, который сам указывает на символ. Как и в предыдущем случае, он понятия не имеет, есть ли другие указатели рядом с тем, на который он должен указывать.

array = malloc(3 * sizeof(char*)); выделяет достаточно места в памяти, чтобы сохранить ровно 3 указателя на символ. Результат распределения будет присвоен array .

В » c » оператор [] , применяемый к указателю, аналогичен оператору, применяемому к переменной массива. Таким array[1] образом, возвращает указатель #2 из памяти, выделенной выше.

array[i] = malloc(10); выделяет память для строки из 10 символов и присваивает результат указателю, на который указывает массив[i]. free(array[i]) освобождает это воспоминание.

В результате вы получаете двухуровневую динамическую структуру.

            |-malloc(3 * sizeof (char*)) == 24 bytes
           |
           V
array --> [0] --> malloc(10) == 10 bytes
          [1] --> malloc(10)
          [2] --> malloc(10)
 

Итак, когда вы освобождаетесь, вам нужно free(array[i]) //0..2 сначала это free(array) сделать, потому что после освобождения массива память, на которую он указывает, тоже становится недействительной, и вы не можете ее использовать.