Почему не законно преобразовывать "указатель на указатель на неконстантный" в "указатель на указатель на константный"
Допустимо преобразовывать указатель на неконстантный в указатель на константный.
Тогда почему не законно преобразовывать указатель на указатель на неконстантный указатель на указатель на константный?
Например, почему следующий код недопустим:
char *s1 = 0;
const char *s2 = s1; // OK...
char *a[MAX]; // aka char **
const char **ps = a; // error!
5 ответов
Из стандарта:
const char c = 'c';
char* pc;
const char** pcc = &pc; // not allowed
*pcc = &c;
*pc = 'C'; // would allow to modify a const object
Игнорируя ваш код и отвечая на принцип вашего вопроса, смотрите эту запись в comp.lang.c FAQ: Почему я не могу передать char ** в функцию, которая ожидает const char **?
Причина, по которой вы не можете назначить
char **
значение дляconst char **
указатель несколько неясен. Учитывая, чтоconst
Спецификатор существует вообще, компилятор хотел бы помочь вам выполнить ваши обещания не изменятьconst
ценности. Вот почему вы можете назначитьchar *
кconst char *
, но не наоборот: "безопасно" добавитьconst
-несс к простому указателю, но было бы опасно убирать его. Однако предположим, что вы выполнили следующие более сложные серии заданий:const char c = 'x'; /* 1 */ char *p1; /* 2 */ const char **p2 = &p1; /* 3 */ *p2 = &c; /* 4 */ *p1 = 'X'; /* 5 */
В строке 3 мы присваиваем
char **
кconst char **
, (Компилятор должен пожаловаться.) В строке 4 мы присваиваемconst char *
кconst char *
; это явно законно. В строке 5 мы модифицируемchar *
указывает на - это должно быть законно. Тем не мение,p1
в конечном итоге указывает наc
, которыйconst
, Это произошло в строке 4, потому что*p2
было правдаp1
, Это было установлено в строке 3, которая является назначением запрещенной формы, и именно поэтому строка 3 запрещена.
И так как ваш вопрос помечен C++, а не C, это даже объясняет, что const
квалификаторы для использования вместо:
(В C++ есть более сложные правила для назначения указателей, соответствующих константам, которые позволяют выполнять больше видов назначений без появления предупреждений, но при этом защищают от непреднамеренных попыток изменить значения констант. C++ по-прежнему не позволяет назначать
char **
кconst char **
, но это позволит вам уйти с назначениемchar **
кconst char * const *
.)
Просто, так как никто не разместил решение, здесь:
char *s1 = 0;
const char *s2 = s1; // OK...
char *a[MAX]; // aka char **
const char * const*ps = a; // no error!
( http://www.parashift.com/c++-faq-lite/const-correctness.html почему)
Проект стандарта C++11 объясняет это в примечании в разделе 4.4
который говорит:
[Примечание: если программа может назначить указатель типа T** указателю типа const T** (т. Е. Если бы была разрешена строка № 1 ниже), программа могла бы непреднамеренно изменить объект const (как это делается на линии № 2). Например,
int main() { const char c = 'c'; char* pc; const char** pcc = &pc; // #1: not allowed *pcc = &c; *pc = 'C'; // #2: modifies a const object }
—Конечная записка]
Интересный связанный вопрос задан int **p1, а const int **p2 правильно сформирован p1 == p2?,
Обратите внимание, что в C++ FAQ также есть объяснение этому, но мне больше нравится объяснение из стандарта.
Соответствующий текст, сопровождающий примечание, выглядит следующим образом:
Конверсия может добавить квалификаторы cv на уровнях, отличных от первого в многоуровневых указателях, при условии соблюдения следующих правил:56
Два типа указателей T1 и T2 похожи, если существует тип T и целое число n > 0, такое что:
T1 - указатель cv1,0 на указатель cv1,1 на · · · cv1,n−1 указатель на cv1, n T
а также
T2 - указатель cv2,0 на указатель cv2,1 на · · · cv2,n−1 указатель на cv2, n T
где каждый cvi,j является const, volatile, const volatile или ничего. N-кортеж cv-квалификаторов после первого в типе указателя, например, cv1,1, cv1,2,..., cv1, n в типе указателя T1, называется квалификационной сигнатурой cv типа указателя, Выражение типа T1 может быть преобразовано в тип T2, если и только если выполняются следующие условия:
- типы указателей похожи.
- для каждого j > 0, если const находится в cv1,j, тогда const находится в cv2,j, и аналогично для volatile.
- если cv1,j и cv2, j различны, то const есть в каждом cv2,k для 0
Здесь нужно отметить два правила:
- Там нет никаких явных бросков между
T*
а такжеU*
если T и U разные типы. - Вы можете разыграть
T*
вT const *
неявно. ("Указатель на T" может быть приведен к "Указатель на const T"). В С ++, еслиT
также является указателем, тогда это правило может быть применено и к нему (цепочка).
Так, например:
char**
означает: указатель на указатель на символ
А также const char**
означает: указатель на указатель на константный символ.
Поскольку указатель на char и указатель на const char - это разные типы, которые отличаются не только по константности, поэтому приведение не допускается. Правильный тип для приведения должен быть константным указателем на символ.
Таким образом, чтобы сохранить правильность const, вы должны добавить ключевое слово const, начиная с самой правой звездочки.
Так char**
может быть приведен к char * const *
и может быть приведен к const char * const *
тоже.
Эта цепочка только C++. В C эта цепочка не работает, поэтому на этом языке вы не можете правильно разыграть более одного уровня указателей const.