问题
I am trying to do a implementation of circular buffer in array. I keep my data in structure and manage it by few methods as push, pop, etc. The program is more or less functional and behave as expected, however I run into errors in my valgrind test. And I am not capable of finding out what is wrong with my code. Although it seems like managing data via pointers in my struct is the crucial problem. I would be very grateful if anyone could point me in the right direction coz I am really lost at this point.
This is how my struct looks like:
typedef struct queue_t{
int* data;
int* end;
int* head;
int* tail;
int max_length;
int cur_length;
} queue_t;
Here are my methods to manage buffer operations:
(Commented code produces pretty much the same errors as 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;
}
This is my main method to test funcionality of mentioned buffer operations:
(queue.h is where my functions are defined)
#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;
}
And finally this is my valgrind output:
==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)
Pointed lines of code are:
72: memcpy(item, queue->head, sizeof(int));
50: memcpy(queue->tail, temp, sizeof(int));
Thanks a lot in advance, I hope someone will be able to show me, what is that bad practice I am doing here :/
回答1:
There's a few problems with this. First, you shouldn't cast the data to an int* because it can be a pointer to anything. In your struct declaration, the data array and all the other pointers should be declared as void** since it points to this void* type that is stored in the array. You don't actually need memcpy at all. You just assign it like this: *(queue->tail) = data;
where data is of type void*. In my opinion, a more clear way would be to just store the head and tail as integers (as an index relative to the array) - then you could do this: queue->data[queue->tail] = data;
without having to deal with the pointers manually.
Right now what you're doing on these lines:
int *item = malloc(sizeof(int*));
memcpy(item, queue->head, sizeof(int));
is allocating some memory that never gets freed but more importantly, you're not actually even returning the value that was stored in queue->head. You're returning the address of the block of memory you just allocated for the item. To get the value, you would have to dereference it with a star, as in: return *item;
Again, what you really want though is a simple assignment: void *item = *(queue->head);
回答2:
Based on signatures of some functions in your code (especially bool push_to_queue(queue_t *queue, void *data) { ...
) I suspect that what you want
is a structure for storing pointers to any data you want. And this structure should behave like a queue. Moreover, you are going to implement it as a circular queue.
The first issue I see with your code is in the design of the queue:
typedef struct queue_t{
int* data;
int* end;
int* head;
int* tail;
int max_length;
int cur_length;
} queue_t;
Most importantly - why would you like to store those pointers in an array of integers (in int* data;
)? Maybe an array of pointers would be better? In C, pointers have the same size no matter what type do they point to - they must be capable of storing any memory address, which on 64-bit operating systems normally means that they occupy 8 bytes (8*8=64). However, I recommend you an array of pointers to void. Why? Because nobody will be ever distracted by the fact that you are using i. e. an array of pointers to int, because that can make people think you are actually storing pointers to integers - by using pointers to void, you make this absolutely clear to anyone who will use this code after you.
Therefore I recommend to create a structure similar to this:
typedef struct queue_t{
void** base;
size_t capacity;
size_t used;
void** head;
void** tail;
} queue_t;
void** base
will point to the first element of the array.size_t capacity
will store the length of the array - how many pointers at most can be stored theresize_t used
will store number of currently stored void pointers.void** head
will point to the next available array element (so when user callspush
, we will store hisdata
to*head
void** tail
will point to the oldest elements of the array (so when user callspop
, we willreturn *tail;
at some point)
Then you can create your structure using function like this:
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;
}
And finally let me show how a push function will look like:
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;
}
And using the same logic you can write pop
function and any other you want.
来源:https://stackoverflow.com/questions/41170018/getting-data-from-pointer-in-struct-invalid-read-write