В чем разница указателя на константу и константного указателя в C
Что проверяют
Для QA такой вопрос проверяет, умеете ли вы читать код на C: на ревью, при тестировании белым ящиком, при разборе падения во встраиваемом ПО. Следом часто просят прочитать объявление вроде const int *const p или найти ошибку в сигнатуре функции.
Как отвечать
Правило одно: const относится к тому, что стоит слева от него, а если слева ничего нет, то к тому, что справа. Объявление удобно читать справа налево.
- указатель на константу:
const int *pили равнозначноеint const *p. Черезpданные менять нельзя,*p = 5не скомпилируется, а самpможно переставить на другой адрес; - константный указатель:
int *const p = &a. Адрес зафиксирован и задаётся при объявлении, а данные через него менять можно:*p = 5работает,p = &bнет; - константный указатель на константу:
const int *const p, нельзя ни то, ни другое.
Практический смысл для тестировщика. Сигнатура size_t strlen(const char *s) это контракт: функция обещает не менять строку. Если функция по требованиям только читает буфер, а принимает его без const, это повод для замечания на ревью. Второе: const int * защищает данные только от записи через этот указатель. Та же память может меняться через другой указатель или из другого потока, поэтому «данные константны» здесь неверная формулировка.
Часть проверок автоматизируется: компилятор с -Wall -Wextra -Wcast-qual предупреждает, когда const отбрасывают приведением, а cppcheck и clang-tidy находят параметры, которые стоило объявить константными.
Где спотыкаются
Путают названия местами. Это встречается даже в статьях: «const pointer» подписывают под const int *, хотя это pointer to const.
Считают, что приведение (int *)p делает запись законной. Если исходный объект объявлен const, запись через такое приведение даёт неопределённое поведение: программа может работать, а может упасть, и тест это ловит не всегда.
Путают, что защищает параметр функции. void f(int *const p) для вызывающего кода ничем не отличается от void f(int *p): указатель и так передаётся копией. Данные вызывающего защищает только const int *.