Правильный тип для динамического массива константных строк
У меня всегда было впечатление, что 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
,
2 ответа
В вашем коде нет ничего плохого. Правила для этого находятся в стандарте C здесь:
6.3.2.3. Указатели
Указатель на void может быть преобразован в или из указателя на любой тип объекта. Указатель на любой тип объекта может быть преобразован в указатель на void и обратно; результат должен сравниваться равным исходному указателю.Для любого квалификатора q указатель на неквалифицированный тип может быть преобразован в указатель на q-квалифицированную версию типа; значения, сохраненные в исходном и преобразованном указателях, должны сравниваться одинаково.
Это означает, что любой указатель на тип объекта (на переменную) может быть преобразован в пустой указатель, если указатель не является квалифицированным (постоянным или изменчивым). Так нормально делать
void* vp;
char* cp;
vp = cp;
Но это не нормально делать
void* vp;
const char* cp;
vp = cp; // not an allowed form of pointer conversion
Все идет нормально. Но когда мы смешиваем указатели на указатели, const
-несс очень запутанный предмет.
Когда у нас есть 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 дает неверную диагностику. Или же вы скомпилировали код как C++, который не допускает неявных преобразований в / из void*
,
Назначение действительно по той же причине, что и следующее назначение.
const char** arr = malloc(10 * sizeof(const char *));
void* p = arr;
Это правило1 объясняет, что, если оба операнда являются типами указателей, на которые указывает левый указатель типа, они должны иметь те же квалификаторы, что и тип, на который указывает правый указатель.
Правый операнд - это указатель, указывающий на тип, который не имеет квалификаторов. Этот тип является указателем типа на константный символ (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 (Цитируется из: ИСО / МЭК 9899:201x 6.5.16.1 Ограничения простого назначения 1)
левый операнд имеет атомарный, квалифицированный или неквалифицированный тип указателя, и (учитывая тип, который будет иметь левый операнд после преобразования в lvalue), один операнд является указателем на тип объекта, а другой - указателем на квалифицированную или неквалифицированную версию void, а тип, на который указывает слева, имеет все квалификаторы типа, на который указывает справа;