5

I am doing an exercise where a character pointer array is functioning as a way to store words. I do not understand why I cannot use 'strcpy' to copy the word 'hoi' to the second element of the array in the main function. When I compile the code I get the message 'program has stopped working' in CodeBlocks.

The functions 'numberOfWordsInDict' and 'printDict' are working properly.

Thanks in advance.

int numberOfWordsInDict(char **dict)
{
    int i, cnt = 0;
    for(i = 0; i < 10; i++)
    {
        if(dict[i] != NULL)
        {
            cnt++;
        }
    }
    return cnt;
}

void printDict(char **dict)
{
    int i = 0;
    printf("Dictionary:\n");
    if(numberOfWordsInDict(dict) == 0)
    {
        printf("The dictionary is empty.\n");
    } else
    {
        for(i = 0; i < 10; i++)
        {
            printf("- %s\n", dict[i]);
        }
    }
}

int main()
{
    char *dict[10] = {
            "aap", "bro ", "jojo", "koe", "kip", 
            "haha", "hond", "    drop", NULL,NULL};

    char *newWord1 = "hoi";
    printDict(dict);
    strcpy(dict[1], newWord1);
    printDict(dict);

    return 0;
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
blabla444
  • 85
  • 5
  • 1
    `dict[1]` points to the first character of a string literal. Modifying a string literal, which `strcpy(dict[1], newWord1)` does, gives undefined behaviour. – Peter Aug 10 '17 at 14:08
  • Thank you all very much! – blabla444 Aug 10 '17 at 14:19
  • This is an incredibly common FAQ. If you check out the [Stack Overflow C FAQ](https://stackoverflow.com/tags/c/info) below "Strings", there are several canonical posts than can be used for further reading/close-as-duplicate. – Lundin Aug 10 '17 at 14:35

5 Answers5

2

The array dict is an array of pointers to string literals.

In this statement

strcpy(dict[1],newWord1);

you are trying to copy one string literal in another string literal.

String literals are non-modifiable in C and C++. Any attempt to modify a strig literal results in undefined behavior of the program.

From the C Standard (6.4.5 String literals)

7 It is unspecified whether these arrays are distinct provided their elements have the appropriate values. If the program attempts to modify such an array, the behavior is undefined.

You should declare a two-dimensional character array if you are going to copy string literals in its elements or a one dimensional array of dynamically allocated character arrays.

The program can look the following way

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

#define N   9

size_t numberOfWordsInDict( char dict[][N] )
{
    size_t n = 0;

    while ( *dict[n] ) ++n;

    return n;
}

void printDict( char dict[][N] )
{
    printf("Dictionary:\n");

    size_t n = numberOfWordsInDict( dict );
    if ( n == 0 )
    {
        printf("The dictionary is empty.\n");
    } 
    else
    {
        for ( size_t i = 0; i < n; i++ )
        {
            printf( "- %s\n", dict[i] );
        }
    }
}

int main(void) 
{
    char dict[10][9] = 
    {
        "aap", "bro ", "jojo", "koe", "kip", "haha", "hond", "    drop"
    };
    char *newWord1 = "hoi";

    printDict(dict);
    strcpy(dict[1], newWord1);
    printDict(dict);

    return 0;
}

The program output is

Dictionary:
- aap
- bro 
- jojo
- koe
- kip
- haha
- hond
-     drop
Dictionary:
- aap
- hoi
- jojo
- koe
- kip
- haha
- hond
-     drop

Or you could use a Variable Length Array.

Keep in mind that according to the C Standard the function main without parameters shall be declared like

int main( void )
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Detail: C does not specify "String literals are non-modifiable". It is UB to attempt to do so. Good that you have emphasized the UB of this. – chux - Reinstate Monica Aug 10 '17 at 14:18
  • @Vlad from Moscow your code is the UB. `char dict[10][9] = { "aap", "bro ", "jojo", "koe", "kip", "haha", "hond", " drop" };` And then you iterate `size_t numberOfWordsInDict( char dict[][N] ) { size_t n = 0; while ( *dict[n] ) ++n; return n; }` You are lucky that you had some kind of zero value past the last element of the table. – 0___________ Aug 10 '17 at 15:11
  • @PeterJ You are wrong. I am not lucky. I am just a qualified unemployed.:) The array is declared as having 10 elements. But only 8 initializers are explicitly provided.. The remaining two elements are initialized with zeroes. That is they contain empty strings. The code is well-formed and correct. – Vlad from Moscow Aug 10 '17 at 18:02
  • @PeterJ Initially in the question the array has two elements with sentinel value NULL. So taking into account that the function is written for an array that has a sentinel value then the array used as the argument shall have a sentinel value. And in my program the array also has a sentinel value. Only instead of NULL its sentinel value is an empty string. So I do not understand what "normal language" you are speaking about. As for me then I am speaking about C and using the approach shown in the question. – Vlad from Moscow Aug 10 '17 at 19:38
  • @Vlad from Moscow so what your function does with: `char dict[10][9] = { "aap", "bro ", "jojo", "koe", "kip", "haha", "hond", " drop", "mod", "bod" };` Will it be the UB or not? – 0___________ Aug 10 '17 at 19:40
  • @PeterJ It counts the number of elements in the array until it encounters an empty string that serves as a sentinel value. – Vlad from Moscow Aug 10 '17 at 19:41
  • @PeterJ There is neither UB. The array is declared as having 10 elements. But only 8 initializers are explicitly provided. In this case the other two elements will be zero initialized. That is they will contain empty strings. as for your array's example then you should append one more element with an empty string. – Vlad from Moscow Aug 10 '17 at 19:43
  • @PeterJ Most of the standard C string functions work with strings that is with character arrays that have sentinel value '\0'. The same way the author of the question used an array with sentinel value NULL. Why may not he use arrays with a sentinel value? All what I did is I showed how correctly to define the array and nothing more. – Vlad from Moscow Aug 10 '17 at 19:48
  • @PeterJ In any case the program is well-formed and correct. There is neither UB. – Vlad from Moscow Aug 10 '17 at 19:53
  • He did not mention any sentinel values in the table. And he was iterating through all the elements of the table (including the last one) – 0___________ Aug 10 '17 at 20:00
  • @PeterJ But in fact he has a sentinel value. Otherwise the definition of the array is unclear. In any case I showed how his problem can be resolved using a two-dimensional array with a sentinel value instead of using magic number 10 with arrays. That is my solution will work with any array. It is an universal solution. While his functions indeed have UB if an array passed as an argument has less than 10 elements. – Vlad from Moscow Aug 10 '17 at 20:02
  • @Vlad from Moscow no he had just empty slots. And he did not treat them as as termination values. He just did not know how to detect it properly (he was checking the strlen and though that strlen of NULL is zero. And derefernicg the NULL was causing UBs – 0___________ Aug 10 '17 at 20:05
  • @PeterJ In this case he must also to skip these slots outputting elements of the array. However he sequentially outputs elements independently on whether it is a real string or a slot. So its solution in any case is wrong. – Vlad from Moscow Aug 10 '17 at 20:07
1

Since "bro" is a string constant and dict[1] points to it, dict[1] points to a string constant.

When you use strcpy(dict[1]), ..., you are trying to modify the thing that dict[1] points to. But it points to a constant. So you are trying to modify a constant.

Constants cannot be modified.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • C has various _constants_ yet does not specify a _string constant_. _string literals_ are not true constants, yet attempting to change them is UB like in `strcpy(dict[1]), ...` – chux - Reinstate Monica Aug 10 '17 at 14:23
0

The memory the pointers point to is read-only as "string" is a string literal.

ForceBru
  • 43,482
  • 10
  • 63
  • 98
0

To my understanding it is impossible to write to string literals.

Codor
  • 17,447
  • 9
  • 29
  • 56
0

You need to reserve storage space in the RW memory and the copy the literals there. You do not have to do it manually :)

#define MAXDICTSIZE 25

int numberOfWordsInDict(char dict[][MAXDICTSIZE], int dictsize)
{
    int i, cnt = 0;
    for (i = 0; i < dictsize; i++)
    {
        if (strlen(dict[i]))
        {
            cnt++;
        }
    }
    return cnt;
}

void printDict(char dict[][MAXDICTSIZE], int dictsize)
{
    int i = 0;
    printf("Dictionary:\n");
    if (numberOfWordsInDict(dict, dictsize) == 0)
    {
        printf("The dictionary is empty.\n");
    }
    else
    {
        for (i = 0; i < dictsize; i++)
        {
            if(strlen(dict[i]))printf("- %s\n", dict[i]);
        }
    }
}

and somewhere in the main()

char dict[10][MAXDICTSIZE] = {
    "aap", "bro ", "jojo", "koe", "kip",
    "haha", "hond", "    drop", "","" };

char *newWord1 = "hoi";
printDict(dict, sizeof(dict)/sizeof(dict[1]));
strcpy(dict[1], newWord1);
printDict(dict, sizeof(dict) / sizeof(dict[1]));
0___________
  • 60,014
  • 4
  • 34
  • 74