2
\$\begingroup\$

I spent some time implementing a generic stack in C using macros. Apart from general bugs and bad practices in my code, I was wondering about the viability of an implementation like this that uses macros. Note that the use of assert is only for this code; if I were coding an actual implementation that would be served to users, I would find a better solution.

stack.h

#ifndef STACK_H
#define STACK_H
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#define stackdef(T, pfx) \
 typedef struct pfx##stack { \
 size_t capacity, N; \
 T *a; \
 } pfx##stack; \
 \
 /* returns a pointer to a stack */ \
 pfx##stack *s##pfx##_create(void) { \
 pfx##stack *st = malloc(sizeof *st); \
 \
 assert(st); \
 st->capacity = 1; \
 st->N = 0; \
 assert((st->a = malloc(sizeof *st->a))); \
 return st; \
 } \
 \
 /* frees all memory that was allocated for the stack */ \
 void s##pfx##_free(pfx##stack *st) { \
 assert(st); \
 free(st->a); \
 free(st); \
 } \
 \
 /* returns true if the stack is empty */ \
 bool s##pfx##_isempty(const pfx##stack *st) { \
 assert(st); \
 return st->N == 0; \
 } \
 \
 /* adds ITEM to the top of the stack */ \
 void s##pfx##_push(pfx##stack *st, T item) { \
 assert(st); \
 if (st->N == st->capacity) { \
 assert((st->a = realloc(st->a, (st->capacity *= 2) * sizeof st->a))); \
 } \
 st->a[st->N++] = item; \
 } \
 \
 /* removes and returns the top element of the stack */ \
 T s##pfx##_pop(pfx##stack *st) { \
 assert(st); \
 assert(st->N > 0); \
 if (st->N == st->capacity / 4) { \
 assert((st->a = realloc(st->a, (st->capacity /= 2) * sizeof st->a))); \
 } \
 return st->a[--(st->N)]; \
 } \
 \
 /* iterates through the stack and performs FN on each element */ \
 void s##pfx##_iterate(pfx##stack *st, void (*fn)(void *)) { \
 for (int i = st->N - 1; i >= 0; --i) { \
 fn(&st->a[i]); \
 } \
 }
#endif

Example client

#include "stack.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
stackdef(char *, str);
int getword(char **s);
int main(void) {
 strstack *q = sstr_create();
 char *s;
 while (getword(&s)) {
 if (s[0] == '-' && s[1] == '0円') {
 printf("%s\n", (s = sstr_pop(q)));
 free(s);
 } else {
 sstr_push(q, s);
 }
 }
 return 0;
}
// read the next word from stdin into S and return its length
int getword(char **s) {
 int c;
 int len = 0;
 assert((*s = malloc(sizeof **s)));
 **s = '0円';
 while ((c = getchar()) == ' ' || c == '\t' || c == '\n') {}
 while (c != ' ' && c != '\t' && c != '\n' && c != EOF) {
 (*s)[len++] = c;
 assert((*s = realloc(*s, (len + 1) * sizeof **s)));
 (*s)[len] = '0円';
 c = getchar();
 }
 ungetc(c, stdin);
 return len;
}
asked Jun 26, 2022 at 21:32
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Overall, your code looks good.

On naming

I would rename stackdef to DEFINE_STACK_TYPE since, in C, macros are UPPER_CASE.

typedef struct pfx##stack { 
 size_t capacity, N; 
 T *a; 
} pfx##stack; 

I would rather do:

typedef struct pfx##_stack { 
 size_t capacity; 
 size_t size; 
 T* storage_array; 
} pfx##_stack;

Note that I added _ in pfx##_stack in order to separate the type name and the stack.

void s##pfx##_free(pfx##stack *st)

I would write rather:

void stack_##pfx##_free(...)

That way, you are more verbose about the fact that the data structure is a stack.

Miscellaneous

if (st->N == st->capacity / 4) { 
 assert((st->a = realloc(st->a, (st->capacity /= 2) * sizeof st->a))); 
} 

Very good. That way, you keep the push/pop operations amortized constant time. However, you should not realloc if st->capacity == 1. Actually, I would define, for example, 8 as the MINIMUM_CAPACITY and rely on it.

Summa summarum

All in all, I had this rewrite in mind:

stack.h

#ifndef STACK_H
#define STACK_H
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
static const size_t MINIMUM_CAPACITY = 8;
#define DEFINE_STACK_TYPE(T, pfx) \
 typedef struct pfx##_stack { \
 size_t capacity; \
 size_t size; \
 T* storage_array; \
 } pfx##_stack; \
 \
 /* returns a pointer to a stack */ \
 pfx##_stack* stack_##pfx##_create(void) { \
 pfx##_stack* stack = malloc(sizeof *stack); \
 \
 assert(stack); \
 stack->capacity = MINIMUM_CAPACITY; \
 stack->size = 0; \
 assert((stack->storage_array = \
 malloc(sizeof(*stack->storage_array) * stack->capacity))); \
 return stack; \
 } \
 \
 /* frees all memory that was allocated for the stack */ \
 void stack_##pfx##_free(pfx##_stack* stack) { \
 assert(stack); \
 free(stack->storage_array); \
 free(stack); \
 } \
 \
 /* returns true if the stack is empty */ \
 bool stack_##pfx##_isempty(const pfx##_stack* stack) { \
 assert(stack); \
 return stack->size == 0; \
 } \
 \
 /* adds ITEM to the top of the stack */ \
 void stack_##pfx##_push(pfx##_stack* stack, T item) { \
 assert(stack); \
 if (stack->size == stack->capacity) { \
 assert((stack->storage_array = \
 realloc(stack->storage_array, \
 (stack->capacity *= 2) * sizeof stack->storage_array))); \
 } \
 stack->storage_array[stack->size++] = item; \
 } \
 \
 /* removes and returns the top element of the stack */ \
 T stack_##pfx##_pop(pfx##_stack* stack) { \
 assert(stack); \
 assert(stack->size > 0); \
 if (stack->size == stack->capacity / 4 \
 && stack->size > MINIMUM_CAPACITY) { \
 /* Very good!:) */ \
 assert((stack->storage_array, \
 realloc(stack->storage_array, \
 (stack->capacity /= 2) * sizeof stack->storage_array))); \
 } \
 return stack->storage_array[--(stack->size)]; \
 } \
 \
 /* iterates through the stack and performs FN on each element */ \
 void stack_##pfx##_iterate(pfx##_stack* stack, void (*fn)(void *)) { \
 for (size_t i = stack->size - 1; i >= 0; --i) { \
 fn(&stack->storage_array[i]); \
 } \
 }
#endif

main.c

#include "stack.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
DEFINE_STACK_TYPE(char*, string)
int getword(char** s);
int main(void) {
 string_stack* q = stack_string_create();
 char* s;
 while (getword(&s)) {
 if (s[0] == '-' && s[1] == '0円') {
 printf("%s\n", (s = stack_string_pop(q)));
 free(s);
 }
 else {
 stack_string_push(q, s);
 }
 }
 return 0;
}
// read the next word from stdin into S and return its length
int getword(char** s) {
 int c;
 int len = 0;
 assert((*s = malloc(sizeof * *s)));
 **s = '0円';
 while ((c = getchar()) == ' ' || c == '\t' || c == '\n') {}
 while (c != ' ' && c != '\t' && c != '\n' && c != EOF) {
 (*s)[len++] = c;
 assert((*s = realloc(*s, (len + 1) * sizeof * *s)));
 (*s)[len] = '0円';
 c = getchar();
 }
 ungetc(c, stdin);
 return len;
}
```
answered Jun 27, 2022 at 4:34
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.