const, вызывающий несовместимый тип указателя. Почему только для двойных указателей?

Эти вопросы были рассмотрены здесь.

Предлагаемый дубликат и текущие ответы не учитывают, почему нет проблем с приведенными первыми примерами. В основном, почему не рассуждение:

"const int ** is a pointer to const int * что отличается от просто int*"

также подать заявку на:

"const int * is a pointer to const int, что отличается от просто int"


Я подхожу к этому под другим углом, чтобы, надеюсь, получить другое объяснение.

Код с примерами.

#include <stdio.h>

void f_a (int const a){

    /*
     *  Can't do:
     *      a = 3;  //error: assignment of read-only parameter ‘a’
     *
     * Explanation: I can't change the value of a in the scope of the function due to the const
    */
    printf("%d\n", a);
}

void f_ptr_a_type1 (int const * ptr_a){
    /*
     * Can do this:
     *     ptr_a’ = 0x3;
     * which make dereferencig to a impossible.
     *     printf("%d\n", * ptr_a’);  -> segfault
     * But const won't forbid it.
     *
     *  Can't do:
     *      *ptr_a’ = 3;  //error: assignment of read-only parameter ‘* ptr_a’
     *
     * Explanation: I can't change the value of a by pointer dereferencing and addignment due to the int const
    */
}

void f_ptr_a_type2 (int * const ptr_a){
    /*
     * Can do this:
     *     *a = 3;
     *
     *  Can't do:
     *      ptr_a = 3;  //error: assignment of read-only parameter ‘ptr_a’
     *
     * Explanation: I can't change the value because the const is protecting the value of the pointer in the funcion scope
    */
}

void f_ptr_ptr_a (int const ** ptr_ptr_a){
    /*
     * Can do this:
     *     ptr_ptr_a = 3;
     *     * ptr_ptr_a = 0x3;
     *
     *  Can't do:
     *      ** ptr_ptr_a = 0x3;  //error: assignment of read-only parameter ‘**ptr_a’
     *
     * Explanation: Makes sense. Just follows the pattern from previous functions.
    */
}

int main()
{
    int a = 7;
    f_a(a);

    int * ptr_a = &a;
    f_ptr_a_type1(&a);
    f_ptr_a_type2(&a);

    int ** ptr_ptr_a = &ptr_a;
    f_ptr_ptr_a(ptr_ptr_a);  //warning: passing argument 1 of ‘f_ptr_ptr_a’ from incompatible pointer type [-Wincompatible-pointer-types]
}

Принятый широко принятый ответ выглядит примерно так:

int ** не то же самое, что const int**, и вы не можете безопасно использовать его

Мой вопрос: почему эта функция вдруг заботится?

Здесь не жаловались, что int не int const:

int a = 7;
f_a(a);

Здесь он не жаловался, потому что int * не является ни int const *, ни int * const:

int * ptr_a = &a;
f_ptr_a_type1(&a);
f_ptr_a_type2(&a);

Но вдруг он начинает жаловаться в случае двойного указателя.

  • Ищете объяснения, используя эту терминологию и пример?

  • Почему функция вдруг начинает беспокоиться о правах на запись чего-то, что находится за пределами ее области действия?


person TheMeaningfulEngineer    schedule 10.07.2017    source источник
comment
Возможный дубликат Что такое разница между const int*, const int * const и int const *?   -  person underscore_d    schedule 10.07.2017
comment
Почему функция вдруг начинает беспокоиться о правах на запись чего-то, что находится за пределами ее области действия? Я бы предпочел, чтобы функции беспокоились, когда они не должны, чем не беспокоились бы, когда должны... :)   -  person underscore_d    schedule 10.07.2017
comment
Также обратите внимание, что вы можете обрабатывать const char ** как const * char *, что представляет собой другую сторону одной и той же медали.   -  person David C. Rankin    schedule 15.07.2017


Ответы (4)


Преобразование, например. char * до const char * всегда безопасно. Через const char * данные, на которые указывает, не могут быть изменены, и все.

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

void foo(const char **bar)
{
    const char *str = "test string";
    *bar = str; // perfectly legal
}

int main(void)
{
    char *teststr[] = {0};
    foo((const char **)teststr);
    // now teststr points to a `const char *`!

    *teststr[0] = 'x'; // <- attempt to modify read-only memory
                       //    ok in this line, there's no const qualifier on teststr!
}

Если бы преобразование из char ** в const char ** при вызове foo() было бы неявным, у вас был бы неявный способ преобразования const.

person Community    schedule 10.07.2017
comment
Какое общее правило нужно помнить? - person Alex Quinn; 05.04.2021

Язык запрещает небезопасные преобразования, т. е. приведение указателя на const объект к указателю на не const объект.

Он не заботится о безопасных преобразованиях, то есть о тех, где вы применяете более строгую квалификацию const, чем на самом деле имеет переданная вещь. Например, когда объект действительно доступен для записи, но вы передаете его функции, которой нужно только прочитать его, нет смысла жаловаться на это.

Ваши примеры показывают последнее:

f_ptr_a_type1(&a);

Здесь написано "Посмотрите const на это int, которое я объявил не-const"

f_ptr_a_type2(&a);

Это просто говорит 'Вот указатель на тот же самый не-const int, и вы сделаете копию этого указателя, который будет const в теле функции (указатель, а не int)"

Что касается того, что ошибки:

f_ptr_ptr_a(ptr_ptr_a);

Это следует из того, что сказал какой-то чувак-программист: упоминаемые типы разные, и хотя C/C++ позволяет передавать указатель на-const там, где ожидается указатель на-не-const, он победил. 'не каскадируйте' это преобразование через более чем один уровень указателя, так как это может быть небезопасно, как позже показал Феликс Палмен.

person underscore_d    schedule 10.07.2017

Почему функция вдруг начинает беспокоиться о правах на запись чего-то, что находится за пределами ее области действия?

Дело не в том, что жалоба возникает с точки зрения параметра функции. Параметр будет вести себя так, как ожидается внутри области действия функции, и на него не повлияет то, что происходит с переменной до того, как она войдет в область действия функции.

void f_ptr_ptr_a (int const ** ptr_ptr_a){
    /*
     *  Can't do:
     *      ** ptr_ptr_a = 3;  //error: assignment of read-only parameter ‘**ptr_a’
    */
}

int const ** ptr_ptr_a входит в область действия копируемой функции по значению, запрещающему изменение ** ptr_ptr_a. Ошибка не имеет ничего общего с тем, какими были переменные после того, как они были скопированы по значению.

Ошибка возникает из-за неявного приведения, происходящего во время вызова функции. Разбивая вызов f_ptr_ptr_a(ptr_ptr_a); получаем:

int const ** ptr_ptr_x = ptr_ptr_a;     //This line causes the warning
f_ptr_ptr_a(ptr_ptr_x);

Ищете объяснения, используя эту терминологию и пример?

Давайте теперь разделим пример на голые основы.

int main()
{
int a = 3;
int * ptr_a = &a;
int ** ptr_ptr_a = &ptr_a;        // I promise here **ptr_ptr_a will always be the same


int const b = 5;
int const * ptr_b = &b;
int const ** ptr_ptr_b = &ptr_b;

ptr_ptr_b = ptr_ptr_a;                  // Warning here: -Wincompatible-pointer-types-discards-qualifiers
printf("%d\n", ** ptr_ptr_b);           // Look at me, I've just changed the value of const ** int.

** ptr_ptr_a = 15;
printf("%d\n", ** ptr_ptr_b);            // I did it again.

}

Компилятор предупреждает нас из-за неявного приведения типов.

int main()
{
    int const a = 3;
    int const * ptr_a = &a;
    int const ** ptr_ptr_a = &ptr_a;        // I promise here **ptr_ptr_a will always be the same

    int const b = 5;
    int const * ptr_b = &b;
    int const ** ptr_ptr_b = &ptr_b;

    ptr_ptr_b = ptr_ptr_a;
    printf("%d\n", ** ptr_ptr_b);           // Look at me, I've just changed the value of const ** int.
                                            // And there were no warnings at all. Har har har har!
}

Есть один вывод, который я могу сделать из этого вопроса и, с текущей точки зрения, ненужных сложностей, которые он вносит.

Всегда помните, что доступ путем разыменования — это не то же самое, что прямой доступ.

Мы видели это здесь. Квалификатор const на самом деле делает то, что должен, не позволяя нам изменить ** ptr_ptr_b с помощью механизма разыменования. Конечно, нам удалось изменить значение только для чтения, но это только потому, что мы слишком многого требуем от бедного const.

Другой пример из ответа Шеу из здесь

const char c = 'A';
char* ptr;
const char** const_ptr = &ptr;  // <-- ILLEGAL, but what if this were legal?
*const_ptr = &c;
*ptr = 'B';  // <- you just assigned to "const char c" above.

printf("%c \n", c);
printf("%c \n", *ptr);

Когда вы говорите you just assigned to "const char c" above, это неправда, просто абстракция ссылок выходит из-под контроля.

person TheMeaningfulEngineer    schedule 14.07.2017

Давайте немного разберемся:

  • const int ** является указателем на const int *.

  • int ** является указателем на int *.

Другими словами, const int ** — это указатель на одно, а int ** — на другое.

person Some programmer dude    schedule 10.07.2017
comment
Обновленный вопрос на основе вашего ответа - person TheMeaningfulEngineer; 10.07.2017
comment
Это упрощенное «объяснение» на самом деле не дает понимания конкретной семантики const, которая имеет больше асимметрии, чем различия между фактическими типами данных (например, int ** против struct abc **). - person dvo; 31.01.2019
comment
Но int* можно неявно преобразовать в const int*, поэтому ваше объяснение не соответствует действительности. - person frankster; 15.05.2019
comment
@frankster Да, int * можно преобразовать в const int *. Но int ** нельзя преобразовать в const int ** (дополнительный уровень косвенности делает два типа несовместимыми). Однако int ** можно преобразовать в int * const *. - person Some programmer dude; 15.05.2019