Понимание выделения памяти в куче - PullRequest
0 голосов
/ 12 сентября 2018

Я пытался понять, как распределяется память в куче, используя malloc, и наткнулся на следующее наблюдение, и я не могу понять причину этого.Было бы здорово, если бы кто-то мог объяснить.

Сначала давайте взглянем на код, который я написал:

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

void print_int_heap(unsigned int *ptr, int len)
{
    printf("PREV_SIZE: [%08x]  SIZE: [%08x]  MEM: [%08x] for INT malloc(%d)\n", *(ptr-2), *(ptr-1), ptr, len);
}
void print_char_heap(char *ptr, int len)
{
    printf("PREV_SIZE: [%08x]  SIZE: [%08x]  MEM: [%08x] for CHAR malloc(%d)\n", *(ptr-2), *(ptr-1), ptr, len);
}

int main() {

    unsigned int *ptr1 = malloc(20);
    print_int_heap(ptr1, 20);
    char *ptr2 = malloc(20)
    print_char_heap(ptr2, 20);
    return 0;
}

Вывод, который я получаю для вышеуказанной программы:

PREV_SIZE: [0x00000000] SIZE: [0x00000019] MEM: [0x0804b008] for INT malloc(20)
PREV_SIZE: [0x00000000] SIZE: [0x00000000] MEM: [0x0804b020] for INT malloc(20)

Я могу понять вывод для int malloc, но я не понимаю, почему значение для размера чанка для char malloc равно 0?

Ответы [ 3 ]

0 голосов
/ 12 сентября 2018

Когда вы выполняете арифметику с указателем, арифметика выполняется в единицах размера объекта, на который указывает указатель.Таким образом, char *ptr, ptr-1 вычитает 1 байт из адреса в ptr.Но unsigned int *ptr, ptr-1 вычитает sizeof(int) из адреса в ptr.

Таким образом, в двух ваших функциях вы не вычитаете одинаковое количество байтов, чтобы добраться до данных учета кучидля блока.

Кроме того, когда вы разыменовываете указатель, он получает доступ только к числу байтов в типе данных указателя.Таким образом, в print_int_heap(), *(ptr-1) возвращает unsigned int, в то время как в print_char_heap() он возвращает одну char.

. Вероятно, вам следует просто написать одну функцию print_heap() и привести аргументна соответствующий тип в вызывающем абоненте.

0 голосов
/ 12 сентября 2018

Если ptr является int*, *(ptr - 1) относится к sizeof(int) байтам непосредственно перед тем, что ptr ссылается.Обычно это будет 32-разрядная величина, начинающаяся за четыре байта до ptr.

Аналогично, если это char*, *(ptr - 1) ссылается на sizeof(char) байтов непосредственно перед тем, что ptr ссылается,sizeof(char) всегда 1;обычно это будет 8-битная величина в одном байте, предшествующем значению ptr.

Это, очевидно, совершенно разные вещи.

Кстати, вам разрешено писать ptr[-1].Но, как показывает приведенный выше анализ, это действительно не то, что вы хотите.Вы хотите привести ptr к указателю на тип данных объекта (ов), который, по вашему мнению, предшествует ptr, вероятно uint32_t.

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

Имейте в виду, что не все реализации malloc делают одно и то же.Вы можете легко найти тот, который хранит длину в другом месте или не совсем.

0 голосов
/ 12 сентября 2018

Из C языка программирования книга ДЕННИС М. РИТЧИ

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

    points to          
    next free
     block       
      |                
   ---------------------------------------
   |       |   size   |                  |
   ---------------------------------------
   |       |          |..address returned to the user
   (ptr-2) (ptr-1)    ptr -->     
                      LSB              MSB

Здесь

void print_int_heap(unsigned int *ptr, int len) {
    printf("PREV_SIZE: [%08x]  SIZE: [%08x]  MEM: [%08x] for INT malloc(%d)\n", *(ptr-2), *(ptr-1), ptr, len);
}

*(ptr-2) печатает значение внутри "next free block", как показано на рисунке выше, и *(ptr-1)печатает значение внутри блока "size", т. е. сколько памяти выделено, & ptr печатает адрес, возвращаемый пользователем.Обратите внимание, что здесь ptr тип равен unsigned int*, поэтому *(ptr-2) означает доступ к данным из 2*sizeof(int) байтов непосредственно перед тем, где ptr указывает.

А здесь

void print_char_heap(char *ptr, int len){
    printf("PREV_SIZE: [%08x]  SIZE: [%08x]  MEM: [%08x] for CHAR malloc(%d)\n", *(ptr-2), *(ptr-1), ptr, len);
}

при доступе*(ptr-1)

    next free
     block        (ptr-1)--> *(ptr-1) prints data from ? marked location.
      |            |  
   ---------------------------------------
   |       | size |? |                  |
   ---------------------------------------
   |       |         |..address returned to the user
                     ptr -->     
                     LSB              MSB

* Тип 1050 * равен char* означает, что при выполнении *(ptr-1) он будет получать доступ к данным из sizeof(char) байтов непосредственно перед тем, куда указывает ptr.

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

valgrind --leak-check=full -v ./your_exe

и проанализировав сообщения valgrind.например, это может показать что-то вроде

==3193== Invalid read of size 4
==3193==    at 0x8048459: print_int_heap
==3193== Invalid read of size 4
==3193==    at 0x8048461: print_int_heap
...