the code doesn't look that wrong, but
 
 * the caller should allways check for errors
 * `if (length < 1)` test unnecessary
 * `srand(time(NULL)` should be done outside
 * error catching (`malloc`) should open no new block
 * `short` isn't really at the high of the time, use it only for serilizing/deserilizing raw binary files/network data
 * use `unsigned` if it makes sense, **the counter can't be negative** and this is not lala-java-land (besides it can be a bit faster but most times it doesn't matter) (side note, yes i am damaged from looking at the assembler output of the compilers)



        #include <string.h>
        #include <time.h>

        char *randstring(int length) {    
            char *string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.-#'?!";
            size_t stringLen = strlen(string);        
            char *randomString;

            randomString = malloc(sizeof(char) * (length +1));

            if (!randomString) {
                return (char*)0;
            }
            
            unsigned int key = 0;

            for (int n = 0;n < length;n++) {            
                key = rand() % stringLen;          
                randomString[n] = string[key];
            }

            randomString[length] = '\0';

            return randomString;
        }