K&R2 solution to exercise 1-18 (discarding trailing whitespace and empty lines)
Don't cast the return value from malloc and family - provided there's a prototype in scope (which there is here, due to our #include &lt;stdlib.h&gt;), the void* result will convert to any pointer type.

Always check whether the returned pointer is a null pointer before using it:

wsbuffer = malloc(buffer_size * sizeof *wsbuffer);
if (!buffer) {
    fputs("Memory allocation failure\n", stderr);
    return EXIT_FAILURE;
}


Robust code needs to take extra care when using realloc().  If we write something like p = realloc(p, new_size); then we have a problem when it fails - p is assigned null, and we have nothing pointing to the memory any more (i.e. a memory leak).  The usual pattern looks something like:

void *new_buf = realloc(wsbuffer, buffer_size * sizeof *wsbuffer);
if (!new_buf) {
    free(wsbuffer);
    fputs("Memory allocation failure\n", stderr);
    return EXIT_FAILURE;
}
wsbuffer = new_buf;




Minor things:


Instead of c==' ' || c=='\t', we might consider isspace(c), remembering that this test includes newlines and other whitespace.
We can store whitespace in an array of char rather than int, since we never need to store EOF into that buffer.
The logic might be simpler if use a larger buffer and read a whole line at a time.  Then we just overwrite the start of the last whitespace found in the line, and print it.  That's likely a bit more efficient than our character-at-a-time operation, too.