**Use-after-free bug:**

In `stack_pop()`:
```
    --s->size;
    void *const top = (char *) s->data + (s->size * s->memb_size);
        
    if (s->size && (s->size <= s->cap / 4)) {
        void *const tmp = realloc(s->data, s->cap / 2 * s->memb_size);
        
        if (tmp) {
            s->data = tmp;
            s->cap /= 2;
        } 
        /* Else do nothing. The original memory is left intact. */
    }
```

The lifetime of `s->data` ends with the call to `realloc()`, assuming it succeeded, so `top` is pointing to a block of memory that might have already been freed whilst shrinking.

GCC 12.3 caught the bug with `-O1 -Wall -Werror -Wpedantic`.

```
In file included from <source>:269:
<source>: In function 'main':
<source>:291:16: error: pointer used after 'realloc' [-Werror=use-after-free]
  291 |         assert(*(size_t *) gstack_pop(stack) == i);
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In function 'stack_pop',
    inlined from 'main' at <source>:291:9:
<source>:238:27: note: call to 'realloc' here
  238 |         void *const tmp = realloc(s->data, s->memb_size * new_cap);
      |                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
```
 
If you remove `-O1` it doesn't detect anything. GCC 13.1 and 13.2 did not detect the bug, of if they did, they did not output anything. 

Valgrind also reports a read error for this in `stack_pop()`.

The solution is to move the statement to the end of the function:

```
return (char *) s->data + (s->size * s->memb_size);
```

This also eliminated the variable `top`.