Lesson 4 - 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
(char
s). 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:
- 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 Ottovordemgentschenfelde is therefore unlucky.
- 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 functionsmalloc()
andfree()
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\nStreet: %s\nAge: %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\nStreet: %s\nAge: %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\nStreet: %s\nAge: %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
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 4x (143.82 kB)
Application includes source codes in language c