указатель на массив не совместим с указателем на массив const? - PullRequest
0 голосов
/ 04 мая 2018

В модуле C (он же модуль компиляции) я хочу иметь некоторые личные данные, но выставить их только для чтения для внешнего мира. Я достигаю этого, имея поле в struct, объявленном в моем .c файле, и функцию, объявленную в моем .h файле, которая возвращает указатель на const для этого поля. Например, это может выглядеть следующим образом для строки :

// header:

typdef struct foo foo;

const char *foostr(const foo *f);

// implementation:

struct foo
{
    char *str;
};

const char *foostr(const foo *f)
{
    return foo->str;
}

Теперь моя проблема в том, что у меня есть массив объектов, которые сами являются массивами. Так что в моем struct у меня есть указатель на массив, и из моей функции я пытаюсь вернуть указатель на соответствующий массив const. Рассмотрим следующий пример кода:

#include <stdlib.h>
#include <stdint.h>

typedef uint8_t shape[64];

typedef struct foo
{
    shape *shapes;
} foo;


foo *createfoo(void)
{
    foo *f = malloc(sizeof *f);
    if (!f) return 0;

    // 10 empty shapes:
    f->shapes = calloc(10, sizeof *(f->shapes));

    if (!f->shapes)
    {
        free(f);
        return 0;
    }

    return f;
}

const shape *fooshapes(const foo *f)
{
    return f->shapes;
}

Компилируя это с gcc -c -std=c11 -Wall -Wextra -pedantic, я получаю следующее предупреждение:

constarr.c: In function ‘fooshapes’:
constarr.c:31:13: warning: pointers to arrays with different qualifiers are incompatible in ISO C [-Wpedantic]
     return f->shapes;
            ~^~~~~~~~

Я понимаю, что двойной указатель не совместим с двойным указателем на const , а также с причиной этого, но я не думаю, что это связано или Это? Итак, почему не разрешается неявно преобразовывать указатель на массив в указатель на массив const? И есть идеи, что мне делать вместо этого?

Теперь я добавил явное приведение, подобное этому:

const shape *fooshapes(const foo *f)
{
    return (const shape *) f->shapes;
}

Это, конечно, заставляет компилятор замолчать, и я почти уверен, что на практике он всегда будет работать правильно. «Константное отверстие» не может существовать в этом случае, так как с массивом не существует неконстантного внутреннего указателя. Но это все еще оставляет мне два дополнительных вопроса:

  • Верно ли мое предположение, что это не приводит к дыре в const правильности?
  • Является ли явное приведение нарушает стандарт здесь?

Ответы [ 4 ]

0 голосов
/ 04 мая 2018

Проблема заключается в том, что при определении типа: массиве, а затем const - при указании указателя на этот тип вы на самом деле получаете const uint8_t(*)[64], который не совместим с uint8_t(*)[64] 1), Корректность констант и указатели на массив ведут себя неловко вместе, см. для примера той же проблемы.

В любом случае, корень проблемы в данном конкретном случае заключается в скрытии массива за typedef. Обычно это не очень хорошая идея. Вы можете исправить это, поместив вместо этого массив в структуру, что также может улучшить общий дизайн. Пример:

typedef struct shape
{
  uint8_t shape[64];
} shape_t;

typedef struct foo
{
  shape_t shapes;
} foo_t;

Теперь вы можете вернуть const shape_t* просто отлично.

При желании вы можете теперь сделать shape_t непрозрачным типом, как foo_t. Или вы можете сделать внутреннюю часть shape_t общедоступной, например, выставив объявление struct в форме публичного заголовка. H.


1) Неявное преобразование между указателем на тип и квалифицированным указателем на тип является единственным допустимым неявным преобразованием.

C11 6.3.2.3/2

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

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

Но это не так, это преобразование из указателя на тип массива в уточненный указатель на тип массива.

Нормативным текстом для совместимых типов в C является глава 6.2.7, которая ссылается только на 6.7.3. Соответствующие части:

C11 6,7,3 / 9

Если спецификация типа массива включает какие-либо квалификаторы типа, тип элемента является таким, а не тип массива.

и С11 6,7,3 / 10

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

Вот почему gcc правильно выдает диагностическое сообщение - указатели не являются идентично квалифицированными версиями.

0 голосов
/ 04 мая 2018

Это действительно та же проблема с двойным указателем, на которую вы ссылаетесь в вопросе.

Вы можете преобразовать указатель в T в указатель в const-T. Но const, примененный к массиву, определяет тип элемента, а не тип массива (C11 6.7.3.9), так что это не то, что вы пытаетесь сделать здесь. Вы не пытаетесь преобразовать указатель в массив массива T в указатель на const-array-of T, а пытаетесь преобразовать указатель в массив-const-T и эти два типа не совместимы в C.

0 голосов
/ 04 мая 2018

указатель на массив не совместим с указателем на массив const?

Да, они несовместимы, вы не можете изменить вложенный квалификатор const, int (*a)[42] не совместим с int const (*b)[42] , связанным , поскольку double ** не совместим с double const **

Верно ли мое предположение, что это не приводит к дыре в правильности констант?

Ну, вы добавляете const квалификатор, так что нет. Фактически, возвращение non const не выдает предупреждение и не нарушает правильность const в соответствии с тем же правилом, которое не позволяет вам добавлять const. Но такой код очень запутан, но не является неопределенным поведением.

shape *fooshapes(const foo *f)
{
    return f->shapes;
}

Является ли явное приведение нарушает стандарт здесь?

Строго , Да, как сказал компилятор, типы несовместимы, однако я не думаю, что это приведет к ошибке в любом реальном классическом оборудовании.

Дело в том, что стандарт не гарантирует, что double *a и double const *b имеют одинаковый размер или даже одинаковое значение, а просто гарантирует, что a == b даст положительное значение. Вы можете рекламировать любой double * до double const *, но, как я уже сказал, вы должны продвигать его Когда вы разыгрываете, вы ничего не продвигаете, потому что массив / указатель вложен. Так что это неопределенное поведение.

Это «вы просто не можете сделать это в C», вы можете спросить, но «что я должен делать?». Ну, я не вижу цели вашей структуры, ни вашего указателя на массив в структуре. Чтобы решить более глубокую проблему вашей структуры данных, вы должны задать другой вопрос, в котором вы больше говорите о том, что вы делаете с этим кодом. Например, ваша функция может сделать что-то вроде этого:

uint8_t const *fooshapes(const foo *f, size_t i)
{
    return f->shapes[i];
}
0 голосов
/ 04 мая 2018

Я не совсем уверен, можно ли это считать хорошей идеей, но иногда я чувствую себя ленивым, чтобы определить макрос "const cast", такой как:

#define CC(_VAR) ((const typeof(_VAR))(_VAR))

Это, конечно, предполагает, что у вас есть компилятор, который поддерживает расширение typeof. Используя эту (сомнительную) конструкцию, вы можете написать

const shape *fooshapes(const foo *f)
{
    return CC(f->shapes);
}

В противном случае C, как правило, не приводится к const неявно. Вы должны явно приводить указатели к константным указателям.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...