Почему моя программа позволяет мне изменять значение const char в C? - PullRequest
0 голосов
/ 19 апреля 2020

Я использую CodeBlocks для Windows в качестве своей IDE. У меня проблема в моей программе, и я хотел бы ее понять. В программе, которую я представляю, я объявляю переменную как char*, и моя программа превращает ее в const char*. Я ожидал бы, что это char* будет доступно только для чтения, но я могу изменить, поскольку не знаю, по какой причине.

Вот мой код:

#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;
}

Ответы [ 3 ]

3 голосов
/ 19 апреля 2020

char *array[5][5] определяет array как модифицируемый двумерный массив указателей на модифицируемый char. Вы можете изменять его элементы и строки, на которые они указывают, также могут быть изменены.

Хотя " " является строковым литералом, который не может быть изменен без вызова неопределенного поведения, C Standard не печатает его как const char[], но как char [], который затухает как char * при сохранении в элементе array.

Когда вы пишете strcpy(array[0][0], array[1][0]) напрямую, вы пытаетесь изменить память, на которую указывает array[0][0] с содержимым строки, на которое указывает array[1][0]. Поскольку вы сохранили адрес строкового литерала в array[0][0], вы фактически пытаетесь изменить память, в которой хранится строковый литерал, который имеет неопределенное поведение .

И наоборот, strdup(array[1][0]) выделяет копию строки, на которую указывает array[1][0], и возвращает указатель, который можно сохранить в array[0][0]. Следующий оператор strcpy(array[0][0], array[1][0]); копирует исходную строку поверх своей копии, что разрешено и не имеет никакого эффекта. Вы можете удалить этот вызов на strcpy().

Желательно объявить array как const char *array[5][5], чтобы strcpy(array[0][0], array[0][1]) выдавало предупреждение, но разрешало array[0][0] = strdup(array[0][1]). Однако обратите внимание, что после того, как вы смешаете строковые литералы и выделенные строки в этом массиве, у вас не будет возможности сказать, что следует освобождать, а что - нет. Вся выделенная память будет освобождена при выходе, но если программы будут работать долгое время и продолжат изменять этот массив, невозможность надлежащего управления памятью вызовет утечки памяти и потенциально неограниченное использование памяти.

Некоторые компиляторы предлагают как расширение для строковых литералов типа const char[] (например: gcc -Wwrite-strings). С этой опцией вы получите предупреждение при сохранении " " в array[0][0]. Использование этого параметра по умолчанию является хорошей практикой, но может потребовать значительных изменений в больших программах, добавляя const во многих местах, процесс, называемый const отравление . Во время этого процесса можно обнаружить скрытые ошибки. В более общем смысле, аргумент указателя в определении функции должен быть объявлен const, если он не используется прямо или косвенно для изменения объекта, на который он указывает. Это помогает читателям понять код и может также помочь компилятору создавать лучший исполняемый код.

2 голосов
/ 19 апреля 2020

У вас есть двумерный массив указателей.

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

Таким образом, эти утверждения

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

не изменили изначально заостренные строки. Они изменили указатели, которые являются их ценностями. Теперь указатель array[0][0] указывает на литерал "AAA", а указатель array[1][0] указывает на литерал "BBB".

В этом утверждении

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

вы опять не изменились строковый литерал, на который указывает указатель array[0][0]. Вы изменили значение самого указателя. После этого оператора указатель array[0][0] указывает на выделенную динамическую память, возвращаемую функцией strdup, а динамически размещенный массив содержит копию строкового литерала, на который указывает указатель array[1][0]. Это то, что делает функция strdup.

Этот оператор

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

является избыточным, поскольку динамически размещенный массив, на который указывает указатель array[0][0], уже хранит копию строкового литерала. на который указывает указатель array[1][0].

Обратите внимание на то, что, например, такое объявление

const char *p = "AAA";

означает, что вы не можете изменить указанный объект, являющийся строковым литералом "AAA ». Но вы можете изменить значение самого указателя, например,

p = "BBB"; 

Этот оператор не изменил строковый литерал "AAA". Он изменил значение указателя p, который теперь указывает на строковый литерал "BBB".

Если вы хотите, чтобы указатель также был неизменяемым, вы должны объявить его как

const char * const p = "AAA";

В этом случае такой оператор

p = "BBB"; 

вызовет ошибку компилятора.

0 голосов
/ 19 апреля 2020

массив - это массив из 25 указателей на символ.

место, где точки массива - это литерал (ы) в постоянной памяти.

Следующая программа показывает, что указатель - это то, что нужно изменить.

#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;
}

вывод программы:

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

Я изменил программу, чтобы сделать ее переносимой. Это не изменило основные понятия.

Примечание: голая ссылка на массив IE "abcd" ухудшает адрес первого байта массива. Следовательно, вложенный l oop, который задает массив 5x5, задается адресами, а не значением, на которое указывает адрес

...