Meir Biton
Meir Biton

Reputation: 27

How to use realloc to shorten a string array size

I've tried to take a certain string, have the string set for a certain size, 50 chars, then after all of the strings gets inputted, it will sort them, then realloc the sizes from 50 chars to the length of the string that the user wrote, if at first I gave it 50 bytes, and somebody entered "hi" it will change to the amount of bytes needed.

#include <stdio.h>

#define MAX_CHARS 50

int main(void)
{
    int i = 0, j = 0;
    char* temp = 0;
    char** names = 0;
    int amount = 0;

    // Getting number of friends from user
    printf("Enter number of friends: ");
    scanf("%d", &amount);
    getchar();

    // Allocating space for the names.
    temp = (char*)malloc(MAX_CHARS * sizeof(char));
    names = (char*)malloc(amount * sizeof(char));

    for (i = 0; i < amount; i++)
    {
        names[i] = (char*)malloc((MAX_CHARS + 1) * sizeof(char));
    }

    // Getting the names from the user
    for (i = 0; i < amount; i++)
    {
        printf("Enter name of friend %d: ", i + 1);
        fgets(names[i], MAX_CHARS - 1, stdin);
    }

    for (i = 0; i < amount; i++)
    {
        for (j = i + 1; j < amount; j++)
        {
            if (strcmp(names[j], names[i]) < 0)
            {
                strcpy(temp, names[j]);
                strcpy(names[j], names[i]);
                strcpy(names[i], temp);
            }
        }
        // Reallocating the 50 bytes space to only the space needed.
        printf("%d", strlen(names[i]));
        (*names)[i] = (char*)realloc((*names)[i], strlen(names[i]) * sizeof(char));
    }

    for (i = 0; i < amount; i++)
    {
        printf("%s", names[i]);
    }

    free(names);
    getchar();
    return 0;
}

Upvotes: 2

Views: 2827

Answers (3)

stare
stare

Reputation: 150

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#define SIZE 50
#define NEWSIZE 25

int main(void)
{
        char *str = malloc(SIZE);

        /* now there are 25 bytes
         * allocated for str, unless
         * an error occurs
         */
        void *tmp = realloc(str, NEWSIZE);
        if (!tmp) {
                perror("ERROR");
                exit(EXIT_FAILURE);
        }
        str = tmp;
        exit(EXIT_SUCCESS);
}

Upvotes: 0

chux
chux

Reputation: 153358

How to use realloc to shorten a string array size

Wrong allocation

//                                v----------v s/b the size of a pointer 
// names = (char*)malloc(amount * sizeof(char));
names = malloc(sizeof *names * amount);
//             ^-----------^ Much easier to code right    

off-by-1 (or 2) errors

// fgets(names[i], MAX_CHARS - 1, stdin);
fgets(names[i], MAX_CHARS + 1, stdin);

// realloc((*names)[i], strlen(names[i]) * sizeof(char));
realloc((*names)[i], (strlen(names[i]) + 1)* sizeof(char));

leaving \n in input

fgets(names[i], MAX_CHARS - 1, stdin);
// add
names[i][strcspn(names[i], "\n")] = '\0'; // to lop off potential \n

Potential UB with mis-matched printf specifier

// printf("%d", strlen(names[i]));
printf("%zu", strlen(names[i]));

Failure to free allocations

// add before `free(names);`
for (i=0; i<amount; i++) free(names[i]);

Inefficient sort

Code swaps names, when only the pointers to the names need swapping. Also consider qsort()


Suggested middle code that leaves out the sort details. Recommend to sort after all names entered.

// Allocating space for the names.

// No need to allocate, a simple array will do.
// Let us double it size to help detect and consume long-ish names
char temp[MAX_CHARS * 2];

names = malloc(sizeof *names * amount);
if (names == NULL) Handle_OutOfMemory();

// Getting the names from the user
for (i = 0; i < amount; i++) {
    printf("Enter name of friend %d: ", i + 1);
    if (fgets(temp, sizeof temp, stdin)) {
      Handle_Unexpected_Eary_EOF();
    }
    temp[strcspn(temp, "\n")] = '\0'; // lop off potential \n 
    size_t len = strlen(temp);
    if (len > MAX_CHARS) Handle_LongLine();

    names[i] = malloc(len + 1);  // only allocated what is needed
    if (names[i] == NULL) Handle_OutOfMemory();
    strcpy(name[i], temp);
}

for (i = 0; i < amount; i++) {
    printf("<%s>\n", names[i]);
}

// Sort by your own code or take time to learn `qsort()`
qsort(names, amount, sizeof names[0], TBD_compare_function);

for (i = 0; i < amount; i++) {
    printf("<%s>\n", names[i]);
    free(names[i]);
}
free(names);

Upvotes: 1

bruno
bruno

Reputation: 32586

names is an array of pointer to char, so in

names = (char*)malloc(amount * sizeof(char));

you do not allocate enough, and later the behavior will be undefined when you will assign it out of it

do (the cast is useless)

names = malloc(amount * sizeof(char*));

Doing

(*names)[i] = (char*)realloc((*names)[i], strlen(names[i]) * sizeof(char));

is invalid because (*names)[i] is a char, do not forget also the place for the null character ending the string, so you want :

names[i] = realloc(names[i], strlen(names[i]) + 1);

note that by definition sizeof(char) is 1

Without checking the result of malloc and realloc you suppose/hope there is enough memory, but this may be false and in that case these functions returns NULL, it is more safe to check that case. That means for the realloc to first save in an auxiliary char* to not lost the current value of names[i] you can continue to use if realloc returns NULL

To do

scanf("%d", &amount);

is dangerous, when the input is invalid you do not know that and amount is not initialized with an undefined behavior when you use it, do for instance

if (scanf("%d", &amount) != 1)
{
   puts("invalid value");
   return -1;
}

Considering how you use names[i] when you do

names[i] = (char*)malloc((MAX_CHARS + 1) * sizeof(char));

you allocate 1 byte too much, you can do

 names[i] = malloc(MAX_CHARS);

Warning doing :

fgets(names[i], MAX_CHARS - 1, stdin);

the probable newline ending your input is saved in names[i], probably you need to remove it. In that case you have to adapt when you print the names to introduce a separator between the names, a space or a newline.

An other way to read but without getting the newline is :

 scanf(" 49%[^\n]", names[i]);

The 49 allows to limit the number of characters written in the array (I removed 1 to let the space for the null char), and the spaces before allows to bypass the spaces at the beginning of the input (here spaces means ' ' but also tab, newline etc). Using that way the name can contains spaces, which is not the case with the format "%49s".

Anyway whatever you use you need to check the input was done, else you do not set the array and when you will use it later the behavior will be undefined.

When you sort your array you do :

strcpy(temp, names[j]);
strcpy(names[j], names[i]);
strcpy(names[i], temp);

but you do not need to copy in deep, just exchange the pointers it it :

char * aux = names[j];

names[j] = names[i];
names[i] = aux;

At the end you want to free the resources, but you do only free(names); so you do not free the other arrays

Upvotes: 2

Related Questions