Black Friday Black Friday
Black Friday ultimate sale! Get up to 80 % extra free points! More information here

Lesson 4 - Dynamic strings and structures in the C language

C and C++ The C language Dynamic memory allocation Dynamic strings and structures in the C language

In the previous lesson, Pointer arithmetic in the C language, we focused on pointer arithmetic. In today's C programming tutorial we're going to focus on strings and structures once again. Except for what we showed in the first lessons of the C basics course, we can work with them dynamically.

Dynamic strings

When we wanted to store a string, we stored it as an array of characters (chars). For this array, we had to specify its exact size, as C arrays require. And specified the size either explicitly:

char text[6] = "hello";

Note the size is larger by 1 due to the zero character '\0'.

Or we left it up to C:

char text[] = "hello";

However, if we want to store text that we don't know yet (for example, user names entered at runtime), we have two options:

  1. Use a fixed-size array of characters to store the texts (of size 21, for example). For users with names shorter than 20 characters we'd be wasting memory. E.g. John Doe uses 8 characters only so 12 of them would remain unused. On the other hand, we'd have to truncate user names longer than 20 characters. Bernd Ottovordemgen­tschenfelde is therefore unlucky.
  2. Use char pointers to store the texts. Since arrays and pointers are similar, we can work with such dynamic strings as we've been used to, even using standard functions. Unfortunately, we'd have to take care of the strings ourselves so the functions malloc() and free() would become our friends. Also, we'd need to use a buffer (see further) to retrieve the strings using standard functions, which complicates the situation. We'd avoid the limit of the option 1, but for us, programmers, the application would be much harder to create.

In practice, both approaches are used for storing strings, each having its advantages and disadvantages. Let's try them:

1. Creating static strings

Creating a static string with the name the user enters would look like this:

char name[21];
printf("Enter your name: ");
scanf(" %20[^\n]", name);
printf("Your name is %s", name);

The result:

c_strings
Enter your name: John Smith
Your name is John Smith

2. Creating dynamic strings

Let's show how creating a string that is exactly as long as the user has entered it would look like. Since we're going to use the scanf() function to read from the console, we need to create an auxiliary static string anyway. The scanf() function will store the text into it. This auxiliary array is often called a buffer. Let's show code and explain it:

char buffer[101];
printf("Enter your name: ");
// Stores the name into auxiliary memory
scanf(" %100[^\n]", buffer);
// Creates a dynamic string
char* name = (char *) malloc(strlen(buffer) + 1);
// Sets the value
strcpy(name, buffer);

printf("Your name is %s", name);
// Frees the memory
free(name);

We store the string from the user into the auxiliary array. The array should be long enough to carry the whole text. Depending on the length of the entered string, we then create a new dynamic string, by allocating a char array in memory. It probably won't surprise you that it's 1 character longer than we need, due to the zero character. We set the value from the buffer to the string using the strcpy() function. Now the dynamic string is ready and we can use it as we are used to. Only when we won't need it anymore, it needs to be freed.

You might be wondering what is the advantage of a dynamic string, since memory is wasted by the buffer we need for reading anyway. If we read and stored a million names, we'd still need a single buffer for it and only in the reading phase (then we can destroy it). Storing so many names dynamically would save lots of memory that would be occupied by unused characters otherwise. The price for that is longer code and the need to keep in mind to free strings created like this. Therefore, this solution can't be stated as universal, although I personally prefer it over static strings.

Passing structures by reference

Now let's move to the promised structures. We passed them by value until now. That means that whenever we stored it into a variable, the structure was copied into it. Let's test it on an example.

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

typedef struct {
    char name[51];
    int age;
    char street[51];
} USER;

int main(int argc, char** argv) {
    USER carl;
    strcpy(carl.name, "Carl Smith");
    strcpy(carl.street, "Evergreen Terrace 5");
    carl.age = 27;

    USER user2 = carl;
    carl.age = 15;
    printf("Name: %s\Street: %s\Age: %d", user2.name, user2.street, user2.age);

    return (EXIT_SUCCESS);
}

The code above creates a user in the carl variable and initializes its values. Then it creates another user structure named user2 and stores Carl in it. Since the structure created like this is a value type, we can assign it simply as this and copy it. Changing the age of Carl then doesn't affect user2 in any way. Finally, we print user2, having the same values as Carl had, even the original age:

c_structures2
Name: Carl Smith
Street: Evergreen Terrace 5
Age: 27

It's worth mentioning that copying the whole variable at once will not work with arrays, as it's, unlike structure, made by a pointer.

Passing by value is quite impractical, especially when we want to change some structure. Let's add a function to the program above that accepts a USER structure as a parameter and adds 1 to its age.

void increaseAge(USER user)
{
    user.age++;
}

Now let's make Carl older and print him:

USER carl;
strcpy(carl.name, "Carl Smith");
strcpy(carl.street, "Evergreen Terrace 5");
carl.age = 27;
increaseAge(carl);
printf("Name: %s\Street: %s\Age: %d", carl.name, carl.street, carl.age);

Nothing will happen:

c_structures2
Name: Carl Smith
Street: Evergreen Terrace 5
Age: 27

Of course, this is because Carl was copied into the function parameter and this copy is what was changed, nothing happened to the original Carl. One solution would be to return the modified Carl and overwrite the previous one with it. However, realize that C has to copy each property of the structure, the processor unnecessarily performs a large amount of instructions. Also, we often use malloc() to create structures in our applications anyway if we want to work with them outside the function in which they were created. (Once a function terminates, C frees the memory used by local variables created in the function, therefore, they don't last and can not be returned).

Let's rewrite the program so it uses pointers:

void increaseAge(USER* user)
{
    user->age++;
}

int main(int argc, char** argv) {
    USER* p_carl = malloc(sizeof(USER));
    strcpy(p_carl->name, "Carl Smith");
    strcpy(p_carl->street, "Evergreen Terrace 5");
    p_carl->age = 27;

    USER* p_user2 = p_carl;
    p_carl->age = 15;
    increaseAge(p_user2);
    printf("Name: %s\Street: %s\Age: %d", p_user2->name, p_user2->street, p_user2->age);

    free(p_carl);
    return (EXIT_SUCCESS);
}

Notice that if we want to get the data from a pointer to a structure, instead of writing the dereference operator (asterisk) before it, we change the dot for the arrow operator (->).

The program output is as follows:

c_structures2
Name: Carl Smith
Street: Evergreen Terrace 5
Age: 16

At first, the application allocates memory for a structure of the USER type and stores its address into the p_carl variable. It sets the user's values and then creates one more pointer to Carl, p_user2. Then it changes Carl's age to 15 and increases the age of the user p_user2 pointer points to by 1 year. Carl will be 16, since his age was set to 15 before. We can see that both pointers point to the same user. Then we print Carl using the p_user2 pointer. We won't forget to free the memory.

Maybe it seems to you as an unnecessary playing with pointers, but it's very important to understand how passing works on these small examples, so that we won't then be lost in larger applications.

The source projects for today's lesson can be downloaded below. Next time, in the lesson Dynamic arrays (vectors) in the C language, we'll learn to create a data structure with unlimited size so we could add new and new items to it. It'll be a dynamic array that is sometimes called a vector. Look forward to it :)


 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Activities (3)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!