Итог
Керниган и Ритч ie ошибаются; попытка изменения const
объектов не определена, не определяется реализацией.
Эти правила применяются только к объектам, изначально определенным с помощью const
.
const
в параметрах функции является рекомендательным, а не принудительно. Это возможно и определено стандартом C для функции, чтобы изменить объект, на который указывает параметр const
, если этот объект не был определен с помощью const
.
Подробности
Процитированный отрывок неверен. Попытка изменить объект, определенный с помощью const
, имеет неопределенное поведение, а не поведение, определяемое реализацией. И это относится только к объектам, определенным с помощью const
, а не к объектам, переданным через const
-квалифицированные указатели, если эти объекты не были изначально определены с помощью const
.
C 2018 6.7.3 7 говорит:
Если предпринята попытка изменить объект, определенный с типом, квалифицированным константой, с помощью lvalue с типом, не квалифицированным константой, поведение будет неопределенным.
Та же формулировка встречается в C 1990 6.5.3.
«Не определено» означает, что стандарт C не предъявляет никаких требований к поведению (C 2018 3.4.3). Это отличается от «определяется реализацией», что означает, что реализация C должна документировать, как делается выбор среди возможностей (C 2018 3.4.1).
Обратите внимание, что это правило применяется только к объектам определяется с const
. 6.7 5 говорит нам, что для идентификаторов объектов определение - это объявление, которое заставляет память зарезервировать для объекта. Если мы объявим int x;
внутри функции, это приведет к тому, что память будет зарезервирована для x
, так что это определение. Однако оператор int strlen(const char[]);
просто объявляет функцию и ее тип параметра. Фактический параметр не объявляется, потому что для него нет имени. Если мы рассмотрим фактическое определение функции, например:
int strlen(const char s[])
{
…
}
, тогда это определение функции включает объявление параметра s
. И он определяет s
; хранилище для самого параметра будет зарезервировано при выполнении функции. Однако этот s
- всего лишь указатель на некоторый объект, адрес которого вызывающий передает. Так что это не определение этого объекта.
До сих пор мы знаем, что правило в 6.7.3 7 говорит нам, что изменение объекта, определенного с помощью const
, имеет неопределенное поведение. Существуют ли какие-либо другие правила относительно функции, изменяющей объект, который она получила через указатель с const
? Есть. Левый операнд оператора присваивания должен быть изменяемым. C 2018 6.5.16 2 говорит:
Оператор присваивания должен иметь изменяемое lvalue в качестве левого операнда.
lvalue, квалифицированное с помощью const
, не возможность изменения, согласно C 2018 6.3.2.1 1. Этот абзац является ограничением в стандарте C, что означает, что для диагностики нарушений требуется реализация C. (Итак, опять же, это поведение не определяется реализацией. Реализация C должна выдавать сообщение.) Операторы ++
и --
, как до, так и после, имеют схожие ограничения.
Таким образом, функция с параметром const char s[]
не может напрямую изменять *s
или s[i]
, по крайней мере, без получения сообщения c диагностики. Однако программе разрешено удалить const
в операторе преобразования, если он изначально не присутствовал. C 2018 6.3.2.3 2 говорит, что мы можем добавить const
:
Для любого квалификатора q , указатель на не- q - квалифицированный тип может быть преобразован в указатель на q -квалифицированную версию типа; значения, хранящиеся в исходном и преобразованном указателях, должны сравниваться равными.
, а затем C 2018 6.3.2.3 7 говорит, что после того, как мы это сделаем, мы можем преобразовать версию const
обратно к исходному типу:
Указатель на тип объекта может быть преобразован в указатель на другой тип объекта ... при повторном преобразовании результат будет равен исходному указателю.
Это означает, что если вызывающая процедура имеет:
int x = 3;
foo(&x);
printf("%d\n", x);
, а foo
это:
void foo(const int *p)
{
* (int *) p = 4;
}
, то это разрешено и определено стандартом C. . Функция foo
удаляет const
и изменяет объект, на который указывает, и будет напечатано «4».
Урок здесь в том, что const
в параметрах функции является рекомендательным, а не C. Он служит двум целям:
const
для параметра функции обычно указывает людям, что функция не будет изменять указанный объект через этот параметр. (Однако есть обстоятельства, не обсуждаемые здесь, когда это указание не выполняется.) - Компилятор будет применять правило, согласно которому указанный объект не может быть изменен с помощью типа
const
. Это предотвращает случайные ошибки, когда типографская ошибка может привести к нежелательному присвоению объекту const
. Однако функции разрешено явно удалить const
и затем попытаться изменить объект.