I tried to implement a generic stack in C using void
pointers and tried to keep it as simple as possible by delegating all responsibility to the caller of the functions and avoiding more sophisticated approaches.
stack.h
#ifndef STACK_H
#define STACK_H
#include <stdbool.h>
struct Stack {
void *data;
struct Stack *next;
};
/*
* We declare a pointer to a Stack structure thereby making use of incomplete
* types. Clients that pull in stack.h will be able to declare variables of type
* pstack which are pointers to pointers to Stack structures.
* */
typedef struct Stack *pstack;
bool is_empty(pstack *s);
void make_empty(pstack *s);
void push(struct Stack **s, void *new_num);
void *pop(pstack *s);
#endif /* STACK_H */
stack.c
#include <stdio.h>
#include <stdlib.h>
#include "stack.h"
bool is_empty(pstack *s) { return !s; }
void make_empty(pstack *s)
{
if (!is_empty(s))
pop(s);
}
void *pop(pstack *s)
{
struct Stack *tmp;
void *i;
if (is_empty(s))
exit(EXIT_FAILURE);
tmp = *s;
i = (*s)->data;
*s = (*s)->next;
free(tmp);
return i;
}
void push(struct Stack **s, void *new_num)
{
struct Stack *new_node = malloc(sizeof(struct Stack));
if (!new_node)
exit(EXIT_FAILURE);
new_node->data = new_num;
new_node->next = *s;
*s = new_node;
}
stackclient.c
#include <stdio.h>
#include <stdlib.h>
#include "stack.h"
int main(void)
{
pstack s1;
void *n;
int i = 1;
int j = 2;
push(&s1, &i);
push(&s1, &j);
n = pop(&s1);
printf("Popped %d from s1\n", *((int *)n));
n = pop(&s1);
printf("Popped %d from s1\n", *((int *)n));
exit(EXIT_SUCCESS);
}
3 Answers 3
I see some things that may help you improve your code.
Use all required #include
s
The stack.c
file has #include <stdio.h>
but it appears to me that no functions from it are needed. On the other hand, it uses the bool
from stdbool.h
but doesn't include it directly. One might argue (correctly!) that because it include stack.h
that it's included indirectly, but I'd advocate that it's also in the implementation file so that it's obvious that the bool
used there is the stdbool.h
version.
Use const
where practical
The is_empty
function does not alter the stack, so that argument should express that notion by using the const
keyword:
bool is_empty(const pstack *s) { return !s; }
Combine typedef and structure declaration
It's a common C idiom to combine the typedef and the stack declaration.
typedef struct Stack {
void *data;
struct Stack *next;
} *pstack;
This keeps them together which helps the reader of the code.
Don't misuse exit
The final statement in main currently is:
exit(EXIT_SUCCESS);
However, code to return EXIT_SUCCESS
is automatically generated if the code reaches the end of main
so this is not needed. Also, it may be more appropriate to return NULL
instead of aborting the program with exit
if too many pop
operations are requested.
Consider object ownership
The stack only stores pointers, but does not "own" the objects to which those pointers point. This means that if you push pointers to stack objects with auto
scope, the stack will contain invalid pointers if the underlying items go out of scope. An alternative would be to create a copy of the object and allow the stack
to own them.
Don't rely on values not set
The pstack
relies on the value being 0, (in is_empty
and make_empty
) but does not explicity set it to that (r any) value. If your code checks for a specific value, it should also set it.
-
\$\begingroup\$ Thanks! Where should I set the value to 0? Do you mean when I declare
pstack s1
inmain()
by settings1 = NULL
at the beginning? \$\endgroup\$lord.garbage– lord.garbage2015年07月09日 15:05:46 +00:00Commented Jul 9, 2015 at 15:05 -
\$\begingroup\$ Yes, set
s1 = NULL
at the beginning and also change tobool is_empty(pstack *s) { return s != NULL; }
\$\endgroup\$Edward– Edward2015年07月09日 15:19:14 +00:00Commented Jul 9, 2015 at 15:19 -
\$\begingroup\$ What I basically did was to implement a
create()
anddestroy()
function:pstack create(void) {pstack s = malloc(sizeof(struct Stack)); if (!s) {exit(EXIT_FAILURE)}; s->next = NULL; return s;}
andvoid destroy(pstack s) {free(s);};
. Thanks for your help! \$\endgroup\$lord.garbage– lord.garbage2015年07月09日 15:34:25 +00:00Commented Jul 9, 2015 at 15:34 -
\$\begingroup\$ "Combine typedef and structure declaration" this is bad advise and suggests that you don't know the meaning of opaque type. I would remove this part of your otherwise good answer, as it is incorrect in the context of creating an opaque type ADT. "Consider object ownership" is related to this: the ADT allocates the objects, since it is impossible for the caller to do so: it cannot create an instance of an incomplete type! \$\endgroup\$Lundin– Lundin2015年07月10日 13:28:44 +00:00Commented Jul 10, 2015 at 13:28
-
\$\begingroup\$ @Lundin: whether the
typedef
andstruct
declaration are on the same line or two different ones has no bearing on whether it's an opaque type or not. \$\endgroup\$Edward– Edward2015年07月10日 13:36:11 +00:00Commented Jul 10, 2015 at 13:36
/*
* We declare a pointer to a Stack structure thereby making use of incomplete
* types. Clients that pull in stack.h will be able to declare variables of type
* pstack which are pointers to pointers to Stack structures.
* */
Few problems here. First, a variable of type pstack
is just a pointer to Stack
structure, not pointer to pointer to as the comment claims. Second, you do not use incomplete type as intended: the client still sees the implementation of struct Stack
, and any change in implementation would result in client code being recompiled for no reason. A standard way to deal with it is to leave
typedef struct Stack stack;
in the header file stack.h
, and declare
struct Stack {
...
};
in the implementation file stack.c
.
-
\$\begingroup\$ Cheers. I know that
pstack
is not a pointer to a pointer but when I declare instack.c
withpstack s1
thens1
will be a pointer to apstack
pointer would it not? \$\endgroup\$lord.garbage– lord.garbage2015年07月09日 17:42:43 +00:00Commented Jul 9, 2015 at 17:42 -
\$\begingroup\$ A declaration
pstack s1;
declares a pointer tostruct Stack
. To get a pointer to apstack
pointer one would declarepstack ** s
. \$\endgroup\$vnp– vnp2015年07月09日 17:46:13 +00:00Commented Jul 9, 2015 at 17:46 -
\$\begingroup\$ Then I'm confused as to why I need to pass
&s1
to my functions instead of justs1
. (That confused me before. An example is mypush()
function which I declared without thetypedef
by usingvoid push(struct Stack **s, int new_num)
.) \$\endgroup\$lord.garbage– lord.garbage2015年07月09日 17:47:20 +00:00Commented Jul 9, 2015 at 17:47 -
\$\begingroup\$ Your functions must modify the argument (the top of stack changes). In C the only way to modify an argument is to pass a pointer to it. \$\endgroup\$vnp– vnp2015年07月09日 17:50:47 +00:00Commented Jul 9, 2015 at 17:50
-
\$\begingroup\$ Ah, I got it.. The functions expect a pointer to a pointer but
pstack
is just a pointer. Hence I need to pass the address of thepstack
pointer in order to get a pointer to a pointer in order to be able to modify the argument. \$\endgroup\$lord.garbage– lord.garbage2015年07月09日 17:51:03 +00:00Commented Jul 9, 2015 at 17:51
This is not a working program, you have several bugs. Also, the program design is not the correct way to implement opaque type.
Program design
- Opaque type should not be typedef:ed to a pointer type. In general, it is very bad practice to hide pointers behind typedefs because it severely reduces readability. Instead, typedef the opaque type as an incomplete struct.
typedef struct stack_t stack_t;
- Opaque type means that the struct definition should not be visible to the caller, so it shouldn't be in the "public" header file! But rather in the "private" c file.
- True opaque type means that the ADT is responsible for allocating all objects, because the caller can't create an object of incomplete type. Therefore you need one create() function and one delete() function which create/delete objects of type
stack_t
. - You probably want to keep track of the size of the data somehow, for example with a data size member in the struct.
exit(EXIT_FAILURE);
in the middle of a function is very questionable practice. Instead, you should implement your functions with a returned error code and let the caller worry about which action to take.
Bugs
- make_empty() doesn't empty the stack, as one may think by reading its name. The
if(!is_empty(s))
needs to bewhile (!is_empty(s))
. - Which leads to the next bug,
pop
doesn't modify the pointer (it can't), so an empty pointer is never set to NULL anywhere. - Somewhere the first/last next pointer must be set to NULL.
Stylistic details
- Minor issue: since struct tags and typedefs reside in different scopes (namespaces), you can use the same name for the struct tag and the typedef. Then the code turns less confusing to read.
- It is usually convention to name types
stack_t
. Depends on coding style. So in the h file you should have something liketypedef struct stack_t stack_t;
- Read up on const correctness. Functions that don't modify a pointed-to object should have the pointer declared as pointer-to-const.
- Since the h file in the public documentation of what a c file does, it is best to put all
#include
in the h file, to show which dependencies your module has on other libraries. - Do not use the boolean logic not
!
operator to compare integers or pointers, it should only be used for boolean logic. Instead, checks against null for a pointer should be made explicit,s == NULL
. - Always use braces
{ }
(aka "compound statements") after every singleif
or loop. People who don't do this will write bugs, sooner or later. See Apple's "goto fail" bug as one perfect example. I don't think the person who wrote that billion dollar bug was a rookie, but rather an arrogant veteran insisting on not using braces, for no good reason. - Always
return
from main() even though C allows the return statement to safely be omitted.
-
\$\begingroup\$ Why do you assume that an opaque type was a design goal? \$\endgroup\$Edward– Edward2015年07月10日 13:52:28 +00:00Commented Jul 10, 2015 at 13:52
-
1\$\begingroup\$ This would be a gret answer if you went a bit more into the why?, especially in the style part were you make some controversial claims (return from main and dont' use
!
) without backing them up. \$\endgroup\$jacwah– jacwah2015年07月11日 01:26:51 +00:00Commented Jul 11, 2015 at 1:26 -
\$\begingroup\$ @Edward Because that's how you properly design C programs with private encapsulation. Why would I assume that the OP doesn't want a proper program design? \$\endgroup\$Lundin– Lundin2015年07月11日 13:05:15 +00:00Commented Jul 11, 2015 at 13:05
-
\$\begingroup\$ @jacwah Because
!
is a boolean operator and for the sake of readability it should only be used on expressions that are "effectively boolean". Good C programmers program as if C had a real, type safe boolean type. Don't mix up booleans with integers or pointers. In any other programming language such things wouldn't even compile. References MISRA-C:2004, MISRA-C:2012 etc. \$\endgroup\$Lundin– Lundin2015年07月11日 13:15:51 +00:00Commented Jul 11, 2015 at 13:15 -
\$\begingroup\$ @Lundin, thanks. Though MISRA states that
!x
as advisory (Rule 13.2) and not required and only in the interest of clarity. It is such a commonC
idiom that I do not necessarily see a problem with this unless required by a specific coding style. \$\endgroup\$lord.garbage– lord.garbage2015年07月14日 15:51:44 +00:00Commented Jul 14, 2015 at 15:51
stack_*
instead. Since this is C and there is no namespace this will reduce the chance of functions' names clash. \$\endgroup\$