I defined a display
function to display the content of a queue:
void display(queue_t* s) {
queue_t* c = s;
int i = c->size;
while (i > 0) {
const char* elem = dequeue(c).value;
printf("Item n°%d : %s\n",i,elem);
i--;
};
};
where queue_t
is defined as follow:
typedef struct queue {
node_t* head;
node_t* tail;
int size;
} queue_t;
and dequeue
is a function that removes a node from the queue and frees it. This function works as intended.
The function display
should display the content of the queue without deleting its content but when testing it, the queue is empty if I call display before removing each element one by one by hand by calling dequeue
. I thought that queue_t* c = s;
would copy the element without any link between c
and s
.
How can I copy the content of s
into c
without any link between the two variables ?
EDIT - MWE
Header file - queue.h
#ifndef QUEUE_H
#define QUEUE_H
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
typedef struct element {
bool type;
const char* value;
} element_t;
typedef struct node {
element_t content;
struct node* previous;
struct node* next;
} node_t;
typedef struct queue {
node_t* head;
node_t* tail;
int size;
} queue_t;
queue_t* init_queue(void);
node_t* init_node(element_t e);
void queue(queue_t* s, element_t e);
element_t dequeue(queue_t* s);
void display(queue_t* s);
#endif
Source code - queue.c
#include "queue.h"
queue_t* init_queue(void) {
queue_t* new = (queue_t*)malloc(sizeof(queue_t));
new->head = NULL;
new->tail = NULL;
new->size = 0;
return new;
};
node_t* init_node(element_t e) {
node_t* new = (node_t*)malloc(sizeof(node_t));
new->content = e;
new->next = NULL;
new->previous = NULL;
return new;
};
void queue(queue_t* s, element_t e) {
node_t* n = init_node(e);
if (s->size == 0) {
s->head = n;
s->tail = n;
s->size = 1;
} else {
n->previous = s->tail;
s->tail = n;
s->size ;
};
};
element_t dequeue(queue_t* s) {
if (s->size == 0) {
element_t empty;
empty.type = true;
empty.value = "0";
return empty;
} if (s->size == 1) {
element_t c = s->head->content;
node_t* old = s->head;
s->head = NULL;
s->size = 0;
s->tail = NULL;
free(old);
return c;
} else {
element_t c = s->tail->content;
node_t* old = s->tail;
s->tail = s->tail->previous;
s->tail->next = NULL;
s->size--;
free(old);
return c;
};
};
void display(queue_t* s) {
queue_t* c = s;
int i = c->size;
while (i > 0) {
const char* elem = dequeue(c).value;
printf("Item n°%d : %s\n",i,elem);
i--;
};
};
Test file - test.c
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include "queue.h"
int main(void) {
element_t e1 = {.type = false, .value = "1"};
element_t e2 = {.type = false, .value = "5"};
element_t e3 = {.type = false, .value = "10"};
queue_t* test = init_queue();
queue(test,e1);
queue(test,e2);
queue(test,e3);
display(test);
element_t e4 = dequeue(test);
printf("%s\n",e4.value);
element_t e5 = dequeue(test);
printf("%s\n",e5.value);
element_t e6 = dequeue(test);
printf("%s\n",e6.value);
element_t e7 = dequeue(test);
printf("%s\n",e7.value);
return 0;
}
Run the test file using
gcc -g -std=c99 -Wall -o test.o -c test.c
gcc -g -std=c99 -Wall -o queue.o -c queue.c
gcc -g -std=c99 -Wall -o test queue.o test.o
CodePudding user response:
I thought that queue_t* c = s; would copy the element without any link between c and s.
No. It only makes a copy of the pointer s
that you passed in the function.
Copying would require you to define "copy semantics", in other words:
What happens if you copy a queue, will the "elements" in your queue be copied too? (This is trivial for primitive types, but not so easy for objects).
If your elements are objects, what copy semantic do they have?
(in the case of strings, you'll need something like strncpy
or memcpy
to copy all characters into a new buffer)
Also, making a "true" (truly independent) copy you will need to make a complete copy of the entire queue.
This means setting up a completely new queue with all needed element data copied.
I'm phrasing it this way, because you certainly cannot re-use the head
, tail
, next
and previous
pointers from your existing queue, otherwise it would not be independent.
And... it's a bad design choice to have a method that is supposedly to be read-only* to modify your queue (data).
To display the contents of your queue you don't need to modify it.
You need to be aware, especially in C, when, how, by whom and for how long your data structures are being accessed. Just a single dangling pointer makes your whole program unpredictable.
*displaying data is considered a read-only operation by most (if not all) programmers.
CodePudding user response:
A few issues ...
- You're leaking memory.
- Passing structs by value doesn't scale.
queue
does not setnext
fors->tail
so forward list traversal doesn't work.queue
is much more complicated than it needs to bedequeue
should be split into two functions:dequeue
anddestroy
display
should just display a list by traversal.init_node
should do a deep copy ofcontent.value
You're leaking memory.
dequeue
doesn't free old->value
.
It returns a copy that has a valid value
.
But, in display
, you do:
const char *elem = dequeue(c).value;
You never free elem
Here is the valgrind
output:
==2168790== Memcheck, a memory error detector
==2168790== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2168790== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==2168790== Command: ./fix1
==2168790==
Item n°3 : 10
Item n°2 : 5
Item n°1 : 1
0
0
0
0
==2168790==
==2168790== HEAP SUMMARY:
==2168790== in use at exit: 24 bytes in 1 blocks
==2168790== total heap usage: 5 allocs, 4 frees, 4,216 bytes allocated
==2168790==
==2168790== LEAK SUMMARY:
==2168790== definitely lost: 24 bytes in 1 blocks
==2168790== indirectly lost: 0 bytes in 0 blocks
==2168790== possibly lost: 0 bytes in 0 blocks
==2168790== still reachable: 0 bytes in 0 blocks
==2168790== suppressed: 0 bytes in 0 blocks
==2168790== Rerun with --leak-check=full to see details of leaked memory
==2168790==
==2168790== For lists of detected and suppressed errors, rerun with: -s
==2168790== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
dequeue
returns "by value" -- this doesn't scale
Add int data[10000000];
to element_t
and watch the stack blow up ;-)
In general, passing around a struct
by value has similar issues in other places.
dequeue
should just dequeue the element.
Do one thing well. dequeue
does a dequeue and a [partial/incomplete] destroy.
We should have a separate function destroy
that fully frees the node_t/element_t
And, dequeue
[as written] should be renamed as pop
or dequeue_back
because it works from tail
to head
.
Also, we'd probably like a dequeue_front
that works from head
to tail
And, it's much more useful if the functions return pointers to the dequeued elements and let the caller use destroy
on them when the caller is finished with them.
display
should just display the queue and not alter/destroy it.
It could be split [after fixup] into two functions (e.g.) display_reverse
and display_forward
Here is the refactored code. It is annotated:
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#ifndef DEEPCOPY
#define DEEPCOPY 1
#endif
typedef struct element {
bool type;
#if DEEPCOPY
char *value;
#else
const char *value;
#endif
} element_t;
typedef struct node {
element_t content;
struct node *previous;
struct node *next;
} node_t;
typedef struct queue {
node_t *head;
node_t *tail;
int size;
} queue_t;
queue_t *init_queue(void);
node_t *init_node(const element_t *e);
void queue(queue_t *s, const element_t *e);
node_t *dequeue_back(queue_t *s);
void display_forward(const queue_t *s);
void display_reverse(const queue_t *s);
queue_t *
init_queue(void)
{
queue_t *new = malloc(sizeof(queue_t));
new->head = NULL;
new->tail = NULL;
new->size = 0;
return new;
}
node_t *
init_node(const element_t *e)
{
node_t *new = malloc(sizeof(node_t));
new->content = *e;
// NOTE/FIX: we should deep copy the element
#if DEEPCOPY
new->content.value = strdup(e->value);
#endif
new->next = NULL;
new->previous = NULL;
return new;
}
void
queue(queue_t *s, const element_t *e)
{
node_t *n = init_node(e);
if (s->size == 0) {
s->head = n;
s->tail = n;
s->size = 1;
}
else {
n->previous = s->tail;
// NOTE/BUG: without this forward traversal doesn't work
#if 1
s->tail->next = n;
#endif
s->tail = n;
s->size ;
}
}
void
destroy_element(element_t *elem)
{
free(elem->value);
// reduce unintentional reuse after free
elem->value = NULL;
}
node_t *
destroy_node(node_t *node)
{
destroy_element(&node->content);
free(node);
node = NULL;
// convenience to caller
return node;
}
node_t *
dequeue_back(queue_t *s)
{
node_t *ret = s->tail;
if (ret != NULL) {
s->tail = ret->previous;
if (s->head == ret)
s->head = ret->next;
s->size -= 1;
}
return ret;
}
void
display_forward(const queue_t *s)
{
int i = 0;
const node_t *node = s->head;
for (; node != NULL; node = node->next) {
printf("Item n°%d : %s\n", i, node->content.value);
i ;
}
}
void
display_reverse(const queue_t *s)
{
int i = s->size;
const node_t *node = s->tail;
for (; node != NULL; node = node->previous) {
printf("Item n°%d : %s\n", i, node->content.value);
i--;
}
}
int
main(void)
{
element_t e1 = {.type = false,.value = "1" };
element_t e2 = {.type = false,.value = "5" };
element_t e3 = {.type = false,.value = "10" };
queue_t *test = init_queue();
queue(test, &e1);
queue(test, &e2);
queue(test, &e3);
printf("display_reverse:\n");
display_reverse(test);
printf("display_forward:\n");
display_forward(test);
for (int i = 4; i <= 7; i) {
node_t *node = dequeue_back(test);
if (node == NULL)
break;
printf("main: %s (dequeue_back)\n", node->content.value);
destroy_node(node);
}
#if 1
free(test);
#endif
return 0;
}
In the above code, I've used cpp
conditionals to denote old vs. new code:
#if 0
// old code
#else
// new code
#endif
#if 1
// new code
#endif
Note: this can be cleaned up by running the file through unifdef -k
Here is the program output:
display_reverse:
Item n°3 : 10
Item n°2 : 5
Item n°1 : 1
display_forward:
Item n°0 : 1
Item n°1 : 5
Item n°2 : 10
main: 10 (dequeue_back)
main: 5 (dequeue_back)
main: 1 (dequeue_back)