简体   繁体   中英

Why does my program let me change a value of a const char in C?

I use CodeBlocks for Windows as my IDE. I'm having a problem in my program, and I would like to understand it. In the program that I'm presenting, I declare a variable as a char* and my program turns it into a const char* . I would expect this char* to be readonly, but I can modify for I don't know what reason.

Here is my code:

#include <stdio.h>
#include <windows.h>

int main() {
    int i, d;
    char *array[5][5]; // This is the variable that I declare as char*
    for (i = 0; i != 5; i++) {
        for (d = 0; d != 5; d++) {
            array[i][d] = "   "; // I can change its value because it is a char* type variable
        }
    }
    for (i = 1; i != 0; i++) {
        printf("%s %s %s %s %s\n", array[0][0], array[0][1], array[0][2], array[0][3], array[0][4]);
        printf("%s %s %s %s %s\n", array[1][0], array[1][1], array[1][2], array[1][3], array[1][4]);
        printf("%s %s %s %s %s\n", array[2][0], array[2][1], array[2][2], array[2][3], array[2][4]);
        printf("%s %s %s %s %s\n", array[3][0], array[3][1], array[3][2], array[3][3], array[3][4]);
        printf("%s %s %s %s %s\n", array[4][0], array[4][1], array[4][2], array[4][3], array[4][4]);
        array[0][0] = "AAA";  // Again I can change its value
        array[1][0] = "BBB";  // And again...
        Sleep(1000);//This is only to see the results
        system("cls");
        if (i == 5) {
            array[0][0] = strdup(array[1][0]); // I was trying to copy two strings using the strcpy() but the program stopped, and the problem was that the destiny string was a const char* type string and that couldn't be, even though I could change it before  
            strcpy(array[0][0], array[1][0]);
        }
    }
    return 0;
}

char *array[5][5] defines array as a modifiable 2D array of pointers to modifiable char . You can modify its elements and the strings they point to can also be modified.

Although " " is a string literal that cannot be modified without invoking undefined behavior, the C Standard does not type it as const char[] , but as char [] , which decays as a char * when stored to an element of array .

When you write strcpy(array[0][0], array[1][0]) directly, you attempt to modify the memory pointed to by array[0][0] with the contents of the string pointed to by array[1][0] . Since you stored the address of a string literal in array[0][0] , you are in fact trying to modify the memory where the string literal is stored, which has undefined behavior .

Conversely, strdup(array[1][0]) allocates a copy of the string pointed to by array[1][0] and returns a pointer which can be stored into array[0][0] . The next statement strcpy(array[0][0], array[1][0]); copies the original string on top of its copy, which is allowed and has no effect. You can remove this call to strcpy() .

It is advisable to declare array as const char *array[5][5] , making strcpy(array[0][0], array[0][1]) produce a warning, but allowing array[0][0] = strdup(array[0][1]) . Note however that once you mix string literals and allocated strings in this array, you will have no way to tell which should be freed and which must not. All allocated memory will be released upon exit, but if the programs runs for a long time and keeps modifying this array, the inability to do proper memory management will force memory leaks and potentially unbounded memory use.

Some compilers offer as an extension to type string literals as const char[] (eg: gcc -Wwrite-strings ). With this option, you would get a warning when you store " " into array[0][0] . Using this setting by default is good practice but may require extensive modifications in large programs, adding const in many places, a process called const poisoning . It is not unusual to discover latent bugs during this process. More generally, a pointer argument in a function definition should be declared const if it is not used directly or indirectly to modify the object it points to. It helps readers understand the code and may also help the compiler produce better executable code.

You have a two-dimensional array of pointers.

char * array[5][5];
^^^^^^

So these statements

    array[0][0] = "AAA";//Again I can change its value
    array[1][0] = "BBB";//And again.

did not change the initially pointed strings. They changed the pointers that is their values. Now the pointer array[0][0] points to the literal "AAA" while the pointer array[1][0] points to the literal "BBB" .

In this statement

array[0][0] = strdup(array[1][0]); 

you again did not change the string literal pointed to by the pointer array[0][0] . You changed the value of the pointer itself. After this statement the pointer array[0][0] points to the allocated dynamically memory returned by the function strdup and the dynamically allocated array contains a copy of the string literal pointed to by the pointer array[1][0] . It is what the function strdup does.

This statement

strcpy(array[0][0], array[1][0]);

is redundant because the dynamically allocated array pointed to by the pointer array[0][0] already stores a copy of the string literal pointed to by the pointer array[1][0] .

Pay attention to that for example a declaration like this

const char *p = "AAA";

means that you may not change the pointed object that is the string literal "AAA". But you may change the value of the pointer itself like for example

p = "BBB"; 

This statement did not change the string literal "AAA" . It changed the value of the pointer p that now points to the string literal "BBB" .

If you want that the pointer also would be non-modifiable then you should declare it like

const char * const p = "AAA";

In this case a statement like this

p = "BBB"; 

will generate a compiler error.

the array is an array of 25 pointers to char.

the place where the array points is the literal(s) in read-only memory.

The following program shows that the pointer is what must be changed.

#include <stdio.h>
//#include <windows.h>
//#include <unistd.h>   // sleep()
//#include <stdlib.h>   // system()
#include <string.h>   // strcpy()

int main( void )
{
    int i, d;
    char *array[5][5]; // declare a 2d array of pointers to char

    // initialize the array
    for (i = 0; i != 5; i++) 
    {
        for (d = 0; d != 5; d++) 
        {
            array[i][d] = "abcd"; // I can change its value because it is a char* type variable
            // NO, this is changing where the pointer points
        }
    }

    for (i = 0; i <2; i++) 
    {
        printf("%s %s %s %s %s\n", array[0][0], array[0][1], array[0][2], array[0][3], array[0][4]);
        printf("%s %s %s %s %s\n", array[1][0], array[1][1], array[1][2], array[1][3], array[1][4]);
        printf("%s %s %s %s %s\n", array[2][0], array[2][1], array[2][2], array[2][3], array[2][4]);
        printf("%s %s %s %s %s\n", array[3][0], array[3][1], array[3][2], array[3][3], array[3][4]);
        printf("%s %s %s %s %s\n", array[4][0], array[4][1], array[4][2], array[4][3], array[4][4]);
        puts("");

        array[0][0] = "defg";  //changes the  the pointer, not the data
    }

    //wait for user input
    int ch;
    while( (ch = getchar()) != '\n' );
    getchar();
    return 0;
}

the output of the program is:

abcd abcd abcd abcd abcd
abcd abcd abcd abcd abcd
abcd abcd abcd abcd abcd
abcd abcd abcd abcd abcd
abcd abcd abcd abcd abcd

defg abcd abcd abcd abcd <-- note the changed pointer now points to the new data
abcd abcd abcd abcd abcd
abcd abcd abcd abcd abcd
abcd abcd abcd abcd abcd
abcd abcd abcd abcd abcd

I modified the program to make it portable. This did not change the underlying concepts.

Note: a bare reference to an array IE "abcd" degrades to the address of the first byte of the array. Therefore, the nested loop that is setting the 5x5 array is being set with addresses, not the value where the address points

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM