Получение данных из указателя в структуре «Недопустимое чтение / запись»

Я пытаюсь выполнить реализацию циклического буфера в массиве. Я сохраняю свои данные в структуре и управляю ими несколькими способами, такими как push, pop и т. Д. Программа более или менее функциональна и ведет себя так, как ожидалось, однако я сталкиваюсь с ошибками в моем испытании valgrind. И я не в состоянии выяснить, что не так с моим кодом. Хотя кажется, что управление данными через указатели в моей структуре является решающей проблемой. Я был бы очень благодарен, если бы кто-нибудь мог указать мне в правильном направлении, потому что я действительно потерялся в этот момент.

Так выглядит моя структура:

typedef struct queue_t{ int* data; int* end; int* head; int* tail; int max_length; int cur_length; } queue_t; 

Вот мои методы управления операциями буфера:
(Комментированный код производит почти те же ошибки, что и memcpy)

 int* increase(int* point, queue_t* queue){ if(point != queue->end){ point = point + sizeof(int*); return point; }else{ return queue->data; } } queue_t* create_queue(int capacity){ queue_t* fifo; fifo = malloc(sizeof(queue_t)); fifo->data = malloc((capacity) * sizeof(int*)); fifo->end = fifo->data + (capacity*sizeof(int*)); fifo->head = fifo->data; fifo->tail = fifo->data; fifo->cur_length = 0; fifo->max_length = capacity; return fifo; } void delete_queue(queue_t *queue){ free(queue->data); free(queue); } bool push_to_queue(queue_t *queue, void *data){ int *temp = (int*) data; //*(queue->tail) = *temp; memcpy(queue->tail, temp, sizeof(int)); free(data); if(queue->max_length != queue->cur_length){ queue->cur_length++; } queue->tail = increase(queue->tail, queue); if(queue->tail == queue->head){ queue->head = increase(queue->head, queue); } return true; } void* pop_from_queue(queue_t *queue){ if(queue->cur_length == 0){ return NULL; } int *item = malloc(sizeof(int*)); //*item = *(queue->head); memcpy(item, queue->head, sizeof(int)); queue->head = increase(queue->head, queue); queue->cur_length--; return item; } 

Это мой основной метод проверки функциональности упомянутых операций буфера:
(queue.h – это где мои функции определены)

 #include "queue.h" void print_int(void* p){ if(p != NULL){ printf("%d\n", *((int*)p)); } else { printf("NULL\n"); } } int main(){ int n = 2; int max = 10; queue_t *q; q = create_queue(n); for(int i = 0; i<max;i++){ int* p = malloc(sizeof(int)); *p = i; if(!push_to_queue(q, (void*)p)){ free(p); exit(101); } } for(int i = 0;i<max;i++){ void* p = pop_from_queue(q); print_int(p); free(p); } delete_queue(q); return 0; } 

И, наконец, это мой выход valgrind:

 ==20293== HEAP SUMMARY: ==20293== in use at exit: 0 bytes in 0 blocks ==20293== total heap usage: 15 allocs, 15 frees, 1,136 bytes allocated ==20293== ==20293== All heap blocks were freed -- no leaks are possible ==20293== ==20293== ERROR SUMMARY: 7 errors from 2 contexts (suppressed: 0 from 0) ==20293== ==20293== 1 errors in context 1 of 2: ==20293== Invalid read of size 4 ==20293== at 0x40097C: pop_from_queue (queue.c:72) ==20293== by 0x400713: main (main.c:30) ==20293== Address 0x52030f0 is 16 bytes before a block of size 4 free'd ==20293== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==20293== by 0x4008B8: push_to_queue (queue.c:51) ==20293== by 0x4006D5: main (main.c:23) ==20293== Block was alloc'd at ==20293== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==20293== by 0x4006B5: main (main.c:21) ==20293== ==20293== ==20293== 6 errors in context 2 of 2: ==20293== Invalid write of size 4 ==20293== at 0x4008AB: push_to_queue (queue.c:50) ==20293== by 0x4006D5: main (main.c:23) ==20293== Address 0x52030d0 is 16 bytes after a block of size 16 alloc'd ==20293== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==20293== by 0x4007FB: create_queue (queue.c:33) ==20293== by 0x40069E: main (main.c:18) ==20293== ==20293== ERROR SUMMARY: 7 errors from 2 contexts (suppressed: 0 from 0) 

Указанными строками кода являются:

 72: memcpy(item, queue->head, sizeof(int)); 50: memcpy(queue->tail, temp, sizeof(int)); 

Большое спасибо заранее, я надеюсь, что кто-то сможет показать мне, что это за плохая практика, которую я здесь делаю: /

Есть несколько проблем с этим. Во-первых, вы не должны передавать данные в int *, потому что это может быть указатель на что угодно. В объявлении структуры массив данных и все остальные указатели должны быть объявлены как void **, поскольку он указывает на этот тип void *, который хранится в массиве. Вам вообще не нужна memcpy. Вы просто назначаете его следующим образом: *(queue->tail) = data; где данные имеют тип void *. На мой взгляд, более понятным способом было бы просто сохранить голову и хвост как целые числа (как индекс относительно массива), – тогда вы могли бы сделать это: queue->data[queue->tail] = data; без необходимости обращаться с указателями вручную.

Прямо сейчас, что вы делаете в этих строках:

 int *item = malloc(sizeof(int*)); memcpy(item, queue->head, sizeof(int)); 

выделяет некоторую память, которая никогда не освобождается, но что более важно, вы даже не возвращаете значение, которое было сохранено в queue-> head. Вы возвращаете адрес блока памяти, который вы только что выделили для элемента. Чтобы получить значение, вам придется разыгрывать его со звездой, как в: return *item; Опять же, то, что вы действительно хотите, это простое назначение: void *item = *(queue->head);

Основываясь на подписях некоторых функций вашего кода (особенно bool push_to_queue(queue_t *queue, void *data) { ... ) Я подозреваю, что вы хотите создать структуру для хранения указателей на любые данные, которые вы хотите. И эта структура должна вести себя как очередь. Более того, вы собираетесь реализовать его как круговую очередь .

Первая проблема, которую я вижу с вашим кодом, находится в дизайне очереди:

 typedef struct queue_t{ int* data; int* end; int* head; int* tail; int max_length; int cur_length; } queue_t; 

Самое главное – зачем вам хранить эти указатели в массиве целых чисел (в int* data; )? Может быть, массив указателей будет лучше? В C указатели имеют одинаковый размер независимо от типа, на который они указывают, – они должны быть способны хранить любой адрес памяти, который в 64-разрядных операционных системах обычно означает, что они занимают 8 байтов (8 * 8 = 64). Однако я рекомендую вам массив указателей на пустоту . Зачем? Поскольку никто не будет отвлекаться на то, что вы используете то есть массив указателей на int, потому что это может заставить людей думать, что вы на самом деле храните указатели на целые числа – с помощью указателей на пустоту вы делаете это абсолютно ясным для всех, кто будет используйте этот код после вас.

Поэтому я рекомендую создать структуру, подобную этой:

 typedef struct queue_t{ void** base; size_t capacity; size_t used; void** head; void** tail; } queue_t; 
  • void** base указывает на первый элемент массива.
  • size_t capacity будет хранить длину массива – сколько указателей может быть сохранено там
  • size_t used будет хранить количество сохраненных в настоящее время указателей void.
  • void** head будет указывать на следующий ansible элемент массива (поэтому, когда пользователь push , мы будем хранить его data в *head
  • void** tail указывает на самые старые элементы массива (поэтому, когда пользователь вызывает pop , мы return *tail; в какой-то момент)

Затем вы можете создать свою структуру, используя следующую функцию:

 queue_t* create_queue(size_t capacity) { queue_t* nq = malloc(sizeof(queue_t)); // Let's allocate the array of pointers to void: nq->base = malloc(sizeof(void*) * capacity); nq->capacity = capacity; nq->used = 0; nq->head = nq->tail = nq->base; return nq; } 

И, наконец, позвольте мне показать, как будет выглядеть функция push:

 bool push(queue_t* queue, void* data) { if(queue == NULL || (queue->used == queue->capacity)) return false; *(queue->head++) = data; // this is equivalent to *(queue->head) = data; queue->head += 1; if(queue->head >= queue->base + queue->capacity) queue->head = queue->base; // We went to far, so we go back. return true; } 

И используя ту же логику, вы можете написать функцию pop и любую другую, какую хотите.