Laboratory: Dynamic Strings.

Gábor Horváth / Zsolt Kohári · 2020.11.05.

String and dynamic memory handling are the main topics.

Preparation for lab:

  • Understand lecture topics on strings, arrays and dynamic memory handling.
  • Review classroom practice topics on pointers and passing parameters by address.

1. Dynamic copy of strings

The widely popular strdup() is used frequently in the practice. This is not a standard function, but it is so useful that everybody writes it for himself/herself. The task of this function is that it makes a copy of the recevied string by dynamic memory allocation, and returns the address of the copy. Of course, the copy must be released later, when it is not used anymore.

In all the problems on this lab use library functions to determine length of the string or to actually copy a string whenever possible. Do not forget to place the string terminator whenever there is no suitable library function and you need to implement low level string manipulation.

  • Write a copystring() function, that behaves like that! For instance, calling str = copystring("apple") creates a dynamic copy of the string "apple" and puts its pointer into str.
  • Write a comment above the function providing a short description on how to use it! (You will need to put such comments above every function in your homework project, too).

  • Write a C program demonstrating the usage of this function! Don't forget to release the array when it is not used any more!

Hint

Take a second look at the str = copystring("apple") call! The function call is followed by storing a pointer: str = .... This is the pointer pointing to the new string. It is important to observe that this is not a string assignment behind the "=" operator, the array of letters is not copied, just the pointer pointing to the array of letters is assigned to str.

Solution

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

/* Copies the string received as a parameter dynamically, and
   returns the pointer pointing to the copy. The allocated
   memory space must be released by the caller. */
char *copystring(char const *str) {
    /* calculate the length of the string */
    int length = strlen(str);

    /* allocate space, +1 for the terminating zero */
    char *newstr;
    newstr = (char*) malloc((length + 1) * sizeof(char));
    if (newstr == NULL)
        return NULL;    /* :( */

    /* copy the letters of the string, now there is enough space for that */
    strcpy(newstr, str);
    return newstr;
}


int main(void) {
    char *x;

    x = copystring("hello, dynamic word!");
    if (x != NULL) {
        printf("%s\n", x);
        free(x);
    }
    return 0;
}

2. Dynamic extension of strings

The strcat() function located in string.h header file concatenates two strings: the strcat(x, y) call adds string y to the end of string x. For the correct operation, the array behind x must be long enough to accomodate the letters of y, too, otherwise the function overindexes the array usually leading to a crash. It is the responsibility of the caller to ensure that the array behind x is big enough, which makes the use of this function rather inconvinient.

Your task is to create a more sophisticated function to concatenate two strings. The first parameter must be a dynamic string! This function should be used like x = stringcat(x, y), hence it does not add the letters y to x, but it creates a new array of appropriate size dynamically, assembles the concatenated string there, releases the dynamic memory of the received x and returns the pointer pointing to the new string. For instance:

char *x;
x = copystring("apple");  /* from the previous task */

x = stringcat(x, "tree");
printf("%s\n", x);    // appletree

free(x);

Now you should write this function. Add a comment above it describing the purpose and the usage of the function! Don't forget to mention in the comment that the first parameter of the function must be dynamically allocated.

Hint

Why does the function return the address of the resulting concatenated string? Because the concatenated string consumes more memory, a new memory allocation is needed, thus string x is relocated in the memory. The original string x is released (that's why x must be allocated dynamically - we need to be able to free it), and the new location is returned.

Solution

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

/* from the previous task */
char *copystring(char const *str) {
    char *newstr = (char*) malloc((strlen(str) + 1) * sizeof(char));
    strcpy(newstr, str);
    return newstr;
}


/*
 * Appends "what" to "target". "target" must be allocated
 * dynamically. Returns with the address of the appended
 * string, and releases the memory behind "target"
 * Usage: x = stringcat(x, ...).
 * The caller is responsible to call free() for the
 * resulting string.
 */
char *stringcat(char *target, char const *what) {
    int newlen = strlen(target) + strlen(what);

    char *newstr = (char*) malloc((newlen + 1) * sizeof(char));
    if (newstr == NULL) return NULL;

    strcpy(newstr, target);
    strcat(newstr, what);

    free(target);
    return newstr;
}

int main(void) {
    char *x;
    x = (char*) malloc((strlen("apple")+1) * sizeof(char));
    strcpy(x, "apple");

    x = stringcat(x, "tree");
    if (x != NULL) {
        printf("%s\n", x);    // appletree
        free(x);
    }
    return 0;
}

3. Substrings of strings

Write a function that receives a string and copies only a part of it, specified by a starting and an ending index! (The received string must be intact.) The start index is the position of the first character to keep and the end index is the position of the first character that it not to keep. (Hence, the interval is closed from the left and open from the right).When the start index is less than 0, or the end index is greater than the length of the string, write an error message to the screen before returning with invalid pointer! The function should return with the address of a dynamically allocated string containing the substring to keep.

Write a C program to demonstrate the usage of the function - test it with several different input parameters, like:

0 1 2 3 4 5 6 7 8 9 10 11
h e l l o ,   w o r l d

Dont forget to put the usual comment above the function!

Hint: the requested substring in the original is not terminated so strcpy can not be used. There is a library function to copy a given number of bytes: memcpy(target, source, how many bytes), which can be used now but it does not terminate the string.

Solution

For simplicity, the solution below makes use of the memcpy() function. This function copies arbitrary data, in the form of memcpy(target, source, how many bytes). This functoin is useful in the task because the substring to keep does not have a terminating zero, thus strcpy is not able to move the characters to the resulting array. The terminating zero must be added to the resulting string at the end.

Instead of using memcpy(), copying the characters by a loop is just as correct.

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

/*
 * Creates a new string from "orig" consisting of characters
 * between positions start and end. The caller is responsible
 * to release the memory behind the result of the function.
 */
char *substring(char const *orig, int start, int end) {
    int orilen = strlen(orig);
    if (start < 0)
        printf(...);
		return NULL;
    if (end > orilen)
        printf(...);
		return NULL;
    int newlen = end - start;
    char *newstr = (char*) malloc((newlen + 1) * sizeof(char));
    if (newstr == NULL)
        return NULL;

    memcpy(newstr, ori + start, newlen * sizeof(char));
    newstr[newlen] = '\0';
    return newstr;
}

int main(void) {
    char *str = substring("hello, world", 3, 9);
    if (str != NULL) {
        printf("[%s]\n", str);
        free(str);
    }
    return 0;
}

4. Removing parts from strings

Write the inverse of the previous function: the part delimited by the start and end indices should be removed from the original text, and the rest is kept! The function should not modify the source string received as a parameter, a brand new string should be created dynamically, and returned by the function.

Write a C program to demonstrate the usage of the function. Don't forget to release the memory at the end.

0 1 2 3 4 5 6 7 8 9 10 11
h e l l o ,   w o r l d

Write the usual comment to the top of the function!

Solution

Regarding function memcpy(), see the description of the solution for the previous task.

The principle of the solution is as follows:

  • We copy the first part of the string to the new array with the memcpy() function. At the end of this array there is no terminating zero yet.
  • Then, continuing from where we have stopped (from position start on) we copy the letters from the end of the string. The original string ends with a terminating zero, thus we can use the strcpy() for this purpose. Using strcpy() has the advanatage that it closes the resulting string with a terminating zero as well.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/*
 * Creates a new string containing where a substring delimited by
 * positions "start" and "end" are cut out. The caller is
 * responsible for deleting the returned array from the memory.
 */
char *cutout(char const *orig, int start, int end) {
    int orilen = strlen(orig);
    if (start < 0)
        printf(...);
		return NULL;
    if (end > orilen)
        printf(...);
		return NULL;

    int newlen = orilen - (start - end);
    char *newstr = (char*) malloc((newlen + 1) * sizeof(char));
    if (newstr == NULL)
        return NULL;

    memcpy(newstr, orig, start * sizeof(char));
    strcpy(newstr + start, orig + end);
    return newstr;
}

int main(void) {
    char *str = cutout("hello, world", 4, 8);
    if (str != NULL) {
        printf("[%s]\n", str);
        free(str);
    }
    return 0;
}

5. Inserting strings into strings

This task is similar to the previous ones: the function receives a string, and creates a new one.

This time, however, the task is not to remove/cut something from a string, but to insert another string to somewhere into the original string. For instance, the result of insertstring("hello!", 5, ", world") is a new string containing hello, world!: the second string has been inserted between letters "o" and "!".

Solution

We could use the memcpy() function here, too, like in the previous exercises.

This time we provide a different approach, though. The function strncat(target, source, n) concatenates at most n characters of string source to the end of string target, and closes it with a terminating zero. The steps are as follows:

  • First we put a terminating zero to the end of the new string, transforming it to an empty string.
  • With the strncat() function we copy the first part (index number of characters) of the original string to the target string. The strncat takes care of the terminating zero for us.
  • To the resulting string we concatenate the string to insert (called what); terminating zero will automatically be at the end.
  • The last strcat() call appends the end of the original string to the end of the tartet string, and terminates it with ''.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/*
 * Insters "what" into "target" at position "index"
 * Returs with the dynamically allocated result string
 * The caller has to free the returned string
 */
char *insertstring(char const *orig, int index, char const *what) {
    int newlen = strlen(orig) + strlen(what);

    char *newstr = (char*) malloc((newlen + 1) * sizeof(char));
    if (newstr == NULL)
        return NULL;

    newstr[0] = '\0';
    strncat(newstr, orig, index);
    strcat(newstr, what);
    strcat(newstr, orig + index);
    return newstr;
}

int main(void) {
    char *str = insertstring("hello!", 5, ", world");
    if (str != NULL) {
        printf("[%s]\n", str);
        free(str);
    }
    return 0;
}