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

В модуле 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?
  • Здесь явное приведение нарушает стандарт?

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

FBergo 04.05.2018 00:56

@FBergo преобразование в неконстантное возможно, но все еще запрещено (неопределенные результаты). Я не пытаюсь защититься от неправильного использования C здесь, это, конечно, невозможно ..

user2371524 04.05.2018 00:58

Ну, clang не жалуется: godbolt.org/g/82j7rm ... Может быть, связанный: stackoverflow.com/a/8909208/4944425

Bob__ 04.05.2018 01:34

@Bob__ спасибо, оба очень интересные :) Так что решение для меня "переключиться на лязг";)

user2371524 04.05.2018 07:13

Тот же вопрос в другом синтаксисе, stackoverflow.com/questions/28062095

M.M 21.11.2019 03:00
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
5
1 241
4

Ответы 4

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

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

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

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

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

Спасибо за Ваш ответ! Я должен был упомянуть, что явное приведение типов, конечно, возможно, но я стараюсь этого избегать - в основном это служит для того, чтобы заглушить предупреждения компилятора. Однако ваше последнее предложение неверно ... преобразование из T * в const T * неявно в C, поэтому я был удивлен, что это другое, когда T является типом массива.

user2371524 04.05.2018 01:11

@FelixPalmen Думаю, ты прав. Я сам заметил это несоответствие, отсюда и глупый макрос. Я просто предположил, что C не приводил указатели, а предупреждения GCC были немного ошибочными. Может, это только указатели на массивы?

Roflcopter4 04.05.2018 02:30

Я немного расширил свой вопрос, извините за то, что вообще не написал больше - это не должно аннулировать ваш ответ, просто вы должны знать.

user2371524 04.05.2018 09:11
typeof не является стандартом C, используйте вместо него _Generic.
Lundin 04.05.2018 10:31

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

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

Спасибо за Ваш ответ! Я предполагал что-то подобное, но §6.7.3 стр. 9 не говорит мне всего этого. Сам массив в любом случае является «своего рода константой», поэтому лазейки, такой как двойной указатель, здесь не существует. «эти два типа несовместимы в C.» <- просто потому, что они не указаны в «совместимых типах», или вы знаете, есть ли где-то указанная причина? Между тем ответ связан Bob__ кажется интересным. ...

user2371524 04.05.2018 07:10

Я немного расширил свой вопрос, извините за то, что вообще не написал больше - это не должно аннулировать ваш ответ, просто вы должны знать.

user2371524 04.05.2018 09:11

pointer to array not compatible to a pointer to 'const' array?

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

Is my assumption correct that this doesn't lead to a hole in const correctness?

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

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

Does the explicit cast violate the standard here?

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

Дело в том, что стандарт не гарантирует, что 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];
}

«Ну, я не вижу цели вашей структуры ни вашего указателя на массив в структуре.» Я объяснил цель во введении к вопросу. Конечно, показанный код не является настоящим кодом, он максимально урезан. Ссылка, которую вы даете, проливает немного света, спасибо, это действительно кажется недостатком в спецификации C (в то время как C++ делает «правильные вещи»).

user2371524 04.05.2018 11:41

@FelixPalmen C++ теряет некоторую гибкость, чтобы делать «правильные вещи», это выбор: «Я хочу иметь некоторые личные данные, но предоставить их только для чтения внешнему миру». не объясняет, что вы хотите делать с этим кодом. В вашем MCVE есть дизайн-поток, поэтому я спрашиваю вас об этом. Невозможно помочь вам найти хорошее решение, не зная, какова реальная цель этой структуры данных. Исправить MCVE так же просто, как удалить весь код, поскольку он ничего не делает.

Stargateur 04.05.2018 11:44

Остальные, очевидно, хорошо понимали цель. И обходной путь, предложенный Lundin, на самом деле послужит этой цели ... Я не очень доволен этим, но похоже, что это лучший вариант, предоставление C, похоже, не поддерживает то, что мне здесь нужно. Показанный код предназначен не для делать чего-то, а для разоблачать некоторых частных данных, доступных только для чтения.

user2371524 04.05.2018 11:48

"Когда вы бросаете, вы ничего не продвигаете, потому что массив / указатель вложен" Я сомневаюсь в этом рассуждении. С указатель на массив нет вложенного указателя ... кстати, разве вы не имели в виду перерабатывать?

user2371524 04.05.2018 11:55

@FelixPalmen Да, я думаю, что я достаточно хорошо понимаю код C, поэтому я хорошо понимаю вашу основную цель. Но вы явно не поняли мой последний пример, если ваша цель - раскрыть некоторые личные данные, я полагаю, вы хотите перебрать их, чтобы сделать что-то похожее на то, что я даю. Другой ответ, который говорит вам использовать его, легко принять, но это не делает ваш код хорошим. Я повторяю в последний раз, каков вариант использования этой функции с такими данными, держу пари, что вы где-то ошибаетесь. Promote / convert похожи, и да, я уверен в том, что сказал.

Stargateur 04.05.2018 11:55

Теперь я наконец понял, к чему вы стремитесь. Если исходить из предположения, что вызывающий код хочет перебрать этот массив, ваше предложение имеет смысл, поэтому получите положительный отзыв :), к сожалению, объяснение всего моего проекта здесь намного превысит объем этого вопроса ... вызывающий код не будет повторяться но для результата используйте несколько memcpy().

user2371524 04.05.2018 12:08

@FelixPalmen "выходит за рамки этого вопроса" да, конечно. «вызывающий код не будет повторяться, но будет использовать некоторые функции memcpy () для результата». может быть, fooshapes() сам справится? Вроде этот fooshapes() не раскрывает свои внутренние данные.

Stargateur 04.05.2018 12:15

Позвольте нам продолжить обсуждение в чате.

user2371524 04.05.2018 12:17

Проблема в том, что, определяя тип массива, а затем определяя 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 общедоступной, например, выставив объявление структуры в общедоступном заголовке shape.h.


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

C11 6.3.2.3/2

For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to the q-qualified version of the type; the values stored in the original and converted pointers shall compare equal.

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

Но это не так, это преобразование из указателя в тип массива в тип квалифицированного указателя в массив.

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

C11 6.7.3 / 9

If the specification of an array type includes any type qualifiers, the element type is so-qualified, not the array type.

и C11 6.7.3 / 10

For two qualified types to be compatible, both shall have the identically qualified version of a compatible type

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

@Stargateur Да, я действительно предлагаю переписать код, удалив массив (указатель на).

Lundin 04.05.2018 10:51

В качестве примечания, использование приведения указателя массива тоже должно работать. Это было решение, которое несколько человек придумали в связанном вопросе, и я не понимаю, как оно нарушит стандарт или вызовет какие-либо проблемы с псевдонимом. Просто возвращение указателей на массивы из функций - это вообще довольно неприятная вещь.

Lundin 04.05.2018 10:54

Пример: #define const_cast(n, arr) _Generic(arr, int(*)[n] : (const int(*)[n])arr ) .

Lundin 04.05.2018 10:56

@Stargateur Нет, поскольку эффективный тип исходного объекта не является константным типом. «Критическое неопределенное поведение» - это сначала приведение к константе, а затем снова отбрасывание константы.

Lundin 04.05.2018 11:03

Нет, вы неверно истолковали это, это не проблема констант, проблема в том, что ничто не запрещает const double * иметь тот же размер или даже то же значение double *, из-за этого вы не можете изменять вложенный указатель квалификатора.

Stargateur 04.05.2018 11:04

@Stargateur Единственная проблема стандарта C с преобразованием указателя массива в квалифицированный указатель массива - 6.3.2.3/7. То есть выравнивание и т.д. Что касается вашего примера здесь, это неверно (и не та же проблема, что и в вопросе), поскольку 6.3.2.3/2 требует, чтобы double* сравнивался с const double*.

Lundin 04.05.2018 11:07
Сравнить равное не означает одинаковое значение. Мне действительно не нравится говорить с вами, когда доходит до строго стандартного вопроса C, вы утверждаете, что эти типы совместимы, несмотря на то, что компиляция очень четко пишет "несовместимый тип". И да, это полностью связано с этим вопросом / проблемой!
Stargateur 04.05.2018 11:10

@Stargateur Потому что const uint8_t(*)[64] по сравнению с uint8_t(*)[64] - это не то же самое, что const double* по сравнению с double*. Последний (указатель на квалифицированные данные) на 100% покрывается 6.3.2.3/2. Первый не упоминается явно, потому что это квалифицированный указатель на данные, который не то же самое, что указатель на квалифицированные данные.

Lundin 04.05.2018 11:16

6.3.2.3/2: For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to the q-qualified version of the type; the values stored in the original and converted pointers shall compare equal.

Lundin 04.05.2018 11:16

Спасибо за этот ответ! Хотя это не полностью проясняет ситуацию для меня (например, Зачем несовместимы ли эти типы? Причина, указанная для двойных указателей, здесь не существует ... и в этом случае литье тоже нормально?) - это дает очень ценная идея для «обходного пути», поэтому имейте положительный отзыв и благодарность :) Заключение моего объекта в «структуру» только для того, чтобы обойти странное ограничение C, мне не кажется идеальным, но это был бы возможный способ . Незначительная придирка, я бы не стал называть тип _t из-за того, что POSIX резервирует это пространство имен ...

user2371524 04.05.2018 11:18

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

Stargateur 04.05.2018 11:19

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

Lundin 04.05.2018 11:29

@Lundin тоже за это спасибо. Под Зачем я имел в виду мотивацию (например, есть наглядный пример для случая двойного указателя, когда неявное преобразование из T ** в const T ** пробивает брешь в гарантии const). Мне не хватает такой вещи для этого случая массива, но теперь предположим, что его просто не существует, и этот случай не был полностью продуман в спецификации C: o

user2371524 04.05.2018 11:44

@Lundin "В качестве примечания, использование приведения указателя массива тоже должно работать." Я тоже в этом почти уверен. Но нарушит ли это стандарт? Если да, я изменю свой код, чтобы использовать "обходной путь структуры" :)

user2371524 04.05.2018 11:51

@FelixPalmen Нет, не будет, как только вы используете явное приведение, единственная часть стандарта, которая применяется, - это 6.3.2.3/7, которая касается только фундаментальных вещей, таких как выравнивание и фактическое представление данных. И не должно быть никаких проблем со строгим псевдонимом, поскольку данные на самом деле представляют собой массив uint8_t.

Lundin 04.05.2018 11:58

Другие вопросы по теме