Подтвердить что ты не робот

Правильный тип для динамического массива константных строк

Мне всегда казалось, что const char **x был правильным типом для динамически распределенного массива константных строк, например:

#include <stdlib.h>

int main()
{
    const char **arr = malloc(10 * sizeof(const char *));
    const char *str = "Hello!";
    arr[0] = str;
    free(arr);
}

Однако при компиляции этого кода с VS2017 я получаю это предупреждение в строке free:

warning C4090: 'function': different 'const' qualifiers

Что-то не так с моим кодом? FWIW, когда я компилирую с GCC, я не получаю никаких предупреждений даже с -Wall -Wextra -pedantic.

4b9b3361

Ответ 1

В коде нет ничего плохого. Правила для этого найдены здесь в стандарте C:

6.3.2.3 Указатели
Указатель на void может быть преобразован в указатель или из указателя на любой тип объекта. Указатель на любой тип объекта может быть преобразован в указатель на void и обратно; результат должен сравните с исходным указателем.

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

Значение того, что любой указатель на тип объекта (переменной) может быть преобразован в указатель void, если только указатель не квалифицирован (const или volatile). Так что хорошо делать

void* vp; 
char* cp; 
vp = cp;

Но это не нормально делать

void* vp; 
const char* cp; 
vp = cp; // not an allowed form of pointer conversion

Пока все хорошо. Но когда мы смешиваем в указателях-указателях, const -ness - очень запутанный предмет.

Когда у нас есть const char** arr, у нас есть указатель на указатель на константу char (подсказка: прочитайте выражение справа налево). Или в стандартной талисмане C: указатель на квалифицированный указатель на тип. arr сам по себе не является квалифицированным указателем! Он просто указывает на один.

free() ожидает указатель на void. Мы можем передать любой указатель на него, если мы не передадим квалифицированный указатель. const char** не является квалифицированным указателем, поэтому мы можем передать его просто отлично.

Ключевым указателем на тип будет char* const*.

Обратите внимание на то, как gcc визжит, когда мы пробуем это:

char*const* arr = malloc(10 * sizeof(char*const*));
free(arr);

gcc -std=c11 -pedantic-errors -Wall - Wextra:

ошибка: передача аргумента 1 из 'free' отбрасывает 'const' от тип целевого указателя

Видимо, Visual Studio дает неверную диагностику. Или, альтернативно, вы скомпилировали код как С++, который не допускает неявных преобразований в /from void*.

Ответ 2

Уступка действительна по той же причине, что и следующее присвоение.

const char** arr = malloc(10 * sizeof(const char *));
void* p = arr;

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

Правильный операнд - это указатель, указывающий на тип, который не имеет каких-либо определителей. Этот тип является типом указателя на const char (const char*). Не позволяйте этому определителю const запутать вас, этот определитель не принадлежит типу указателя.

Левый операнд - это указатель, указывающий на тип, который также не имеет каких-либо определителей. Тип недействителен. Таким образом, назначение действительно.

Если указатель указывает на тип, имеющий квалификаторы, то присваивание недействительно:

const char* const* arr = malloc(10 * sizeof(const char *));
void* p = arr;    //constraint violation

Правильный операнд - это указатель, указывающий на тип с квалификатором const, этот тип является указателем типа const на const char (const char* const).

Левый операнд - это указатель, указывающий на тип без каких-либо классификаторов, этот тип является типом void. Назначение нарушает ограничение 1.


1 (Цитируется по: ISO/IEC 9899: 201x 6.5.16.1 Простые назначения Ограничения 1)
левый операнд имеет атомный, квалифицированный или неквалифицированный тип указателя и (учитывая тип, который будет иметь левый операнд после преобразования lvalue), один операнд является указателем к типу объекта, а другой - указатель на квалифицированную или неквалифицированную версию void, а тип, на который указывает левый, имеет все квалификаторы типа, указывающего на справа;