Какой смысл в константных указателях? - PullRequest
145 голосов
/ 10 октября 2011

Я говорю не об указателях на константные значения, а о самих указателях констант.

Я изучаю C и C ++ за пределами самых простых вещей, и до сегодняшнего дня я понял, что указатели передаются по значениюфункции, что имеет смысл.Это означает, что внутри функции я могу заставить скопированный указатель указывать на какое-то другое значение, не влияя на исходный указатель от вызывающей стороны.

Так какой смысл иметь заголовок функции, который говорит:

void foo(int* const ptr);

Внутри такой функции вы не можете заставить ptr указывать на что-то другое, потому что оно const и вы не хотите, чтобы оно было изменено, но функция, подобная этой:

void foo(int* ptr);

Работает так же хорошо!потому что указатель копируется в любом случае, и указатель в вызывающей стороне не затрагивается, даже если вы измените копию.Так в чем же преимущество const?

Ответы [ 17 ]

199 голосов
/ 10 октября 2011

const - это инструмент, который вы должны использовать для реализации очень важной концепции C ++:

Находите ошибки во время компиляции, а не во время выполнения, заставляя компилятор применять то, что вы имеете в виду.

Даже если это не меняет функциональность, добавление const генерирует ошибку компилятора, когда вы делаете то, что не хотели делать. Представьте себе следующую опечатку:

void foo(int* ptr)
{
    ptr = 0;// oops, I meant *ptr = 0
}

Если вы используете int* const, это приведет к ошибке компилятора, поскольку вы меняете значение на ptr. Добавление ограничений через синтаксис - это вообще хорошо. Только не принимайте это слишком далеко - пример, который вы привели, - это случай, когда большинство людей не удосуживаются использовать const.

75 голосов
/ 10 октября 2011

Я рекомендую использовать только только const аргументов, потому что это позволяет проводить больше проверок компилятором: если я случайно переназначу значение аргумента внутри функции, компилятор укусит меня.

Я редко использую переменные, чистее создавать новые переменные для хранения новых значений, поэтому по существу все мои объявления переменных const (за исключением некоторых случаев, таких как переменные цикла, где const может помешать код с работы).

Обратите внимание, что это имеет смысл только в определении функции. Он не принадлежит объявлению 1015 *, которое видит пользователь. И пользователю все равно, использую ли я const для параметров внутри функции.

Пример:

// foo.h
int frob(int x);
// foo.cpp
int frob(int const x) {
   MyConfigType const config = get_the_config();
   return x * config.scaling;
}

Обратите внимание, как аргумент и локальная переменная const. не требуется , но с функциями, которые даже немного больше, это неоднократно спасало меня от ошибок.

20 голосов
/ 10 октября 2011

Ваш вопрос затрагивает нечто более общее: должны ли аргументы функции быть константными?

Константность значений аргументов (например, вашего указателя) является подробностью реализации , и она не является частью объявления функции. Это означает, что ваша функция всегда такова:

void foo(T);

Реализация функции * полностью зависит от того, хочет ли она использовать переменную аргумента functions-scope в изменяемой или постоянной форме:

// implementation 1
void foo(T const x)
{
  // I won't touch x
  T y = x;
  // ...
}

// implementation 2
void foo(T x)
{
  // l33t coding skillz
  while (*x-- = zap()) { /* ... */ }
}

Итак, следуйте простому правилу, чтобы никогда не вставлять const в объявление (заголовок), и помещать его в определение (реализацию), если вы не хотите или не хотите изменять переменную.

16 голосов
/ 10 октября 2011

Спецификатор const верхнего уровня отбрасывается в объявлениях, поэтому объявления в вопросе объявляют точно такую ​​же функцию. С другой стороны, в определении (реализация) компилятор проверит, что если вы пометите указатель как const, он не будет изменен внутри тела функции.

14 голосов
/ 10 октября 2011

Ключевое слово const очень много, оно довольно сложное. Как правило, добавление большого количества const в вашу программу считается хорошей практикой программирования, поищите в Интернете «правильность const», и вы найдете много информации об этом.

Ключевое слово const - это так называемый "квалификатор типа", другие volatile и restrict. Как минимум volatile следует тем же (сбивающим с толку) правилам, что и const.


Прежде всего, ключевое слово const служит двум целям. Наиболее очевидным является защита данных (и указателей) от преднамеренного или случайного неправильного использования, делая их доступными только для чтения. Любая попытка изменить переменную const будет обнаружена компилятором во время компиляции.

Но в любой системе, имеющей постоянную память, есть и другая цель, а именно - обеспечить выделение определенной переменной внутри такой памяти - например, EEPROM или флэш-память. Они известны как энергонезависимые воспоминания, NVM. Переменная, размещенная в NVM, будет, конечно, следовать всем правилам переменной const.

Существует несколько различных способов использования ключевого слова const:

Объявление постоянной переменной.

Это можно сделать как

const int X=1; or
int const X=1;

Эти две формы полностью эквивалентны . Последний стиль считается плохим стилем и не должен использоваться.

Причина, по которой вторая строка считается плохим стилем, возможно потому, что "спецификаторы класса хранения", такие как static и extern, также могут быть объявлены после фактического типа, int static и т. Д. Но выполнение поэтому для спецификаторов класса хранения комитет C отметил как устаревшую функцию (черновик ISO 9899 N1539, 6.11.5). Следовательно, для согласованности не следует также писать классификаторы типов таким способом. Это не преследует никакой другой цели, кроме как сбить с толку читателя.

Объявление указателя на постоянную переменную.

const int* ptr = &X;

Это означает, что содержимое «X» не может быть изменено. Это нормальный способ объявления указателей подобным образом, в основном как часть параметров функции для «правильности констант». Поскольку 'X' на самом деле не нужно объявлять как const, это может быть любая переменная. Другими словами, вы всегда можете «обновить» переменную до const. Технически, C также позволяет понизить const до простой переменной с помощью явных типов, но это считается плохим программированием, и компиляторы обычно выдают предупреждения против него.

Объявить постоянный указатель

int* const ptr = &X;

Это означает, что сам указатель является константой. Вы можете изменить то, на что он указывает, но вы не можете изменить сам указатель. Это не имеет много применений, есть несколько, например, обеспечение того, чтобы указатель-указатель (указатель-на-указатель) не менял свой адрес при передаче в качестве параметра функции. Вам придется написать что-то не слишком читабельное, например:

void func (int*const* ptrptr)

Я сомневаюсь, что многие программисты на C могут получить const и * прямо там. Я знаю, я не могу - мне пришлось проверить с GCC. Я думаю, именно поэтому вы редко когда-либо видите этот синтаксис для указателя на указатель, даже если это считается хорошей практикой программирования.

Указатели констант также можно использовать для гарантии того, что переменная-указатель сама объявлена ​​в постоянной памяти, например, вы можете объявить какую-то таблицу поиска на основе указателей и выделить ее в NVM.

И, конечно, как указывают другие ответы, константные указатели также могут быть использованы для обеспечения "правильной константности".

Объявить постоянный указатель на постоянные данные

const int* const ptr=&X;

Это два вышеописанных типа указателей, со всеми атрибутами обоих.

Объявление функции-члена только для чтения (C ++)

Поскольку это тег C ++, я должен также упомянуть, что вы можете объявить функции-члены класса как const. Это означает, что функции не разрешается изменять любой другой член класса при вызове, что одновременно предотвращает случайные ошибки программиста класса, но также сообщает вызывающей функции-члену, что они не будут ничего портить позвонив по телефону Синтаксис:

void MyClass::func (void) const;
13 голосов
/ 10 октября 2011

Вы правы, для звонящего это абсолютно не имеет значения. Но для автора функции это может быть система безопасности: «Хорошо, мне нужно убедиться, что я не обращаю внимание на неправильную вещь». Не очень полезно, но не бесполезно.

По сути, это то же самое, что int const the_answer = 42 в вашей программе.

8 голосов
/ 10 октября 2011

... сегодня я понял, что указатели передаются по значению функциям, что имеет смысл.

(imo) это действительно не имеет смысла по умолчанию. более разумное значение по умолчанию - указатель без переназначения (int* const arg). то есть я бы предпочел, чтобы указатели, передаваемые в качестве аргументов, были неявно объявлены как const.

Так в чем же преимущество const?

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

так что проще перейти по неизменяемому адресу и создать копию (в тех нетипичных случаях), когда вы хотите изменить адрес, на который указывает аргумент:

void func(int* const arg) {
    int* a(arg);
    ...
    *a++ = value;
}

добавление, что local практически бесплатен, и это уменьшает вероятность ошибок, улучшая читабельность.

на более высоком уровне: если вы манипулируете аргументом как массивом, клиенту обычно проще и менее подвержено ошибкам объявлять аргумент как контейнер / коллекцию.

В общем, добавление const к значениям, аргументам и адресам - хорошая идея, потому что вы не всегда понимаете побочные эффекты, которые компилятор успешно применяет. поэтому он так же полезен, как и const, как и в некоторых других случаях (например, вопрос похож на «Почему я должен объявлять значения const?»). К счастью, у нас также есть ссылки, которые нельзя переназначить.

6 голосов
/ 12 октября 2011

Если вы выполняете программирование встроенных систем или драйверов устройств, где у вас есть устройства с отображенной памятью, то часто используются обе формы const, одна для предотвращения переназначения указателя (так как он указывает на фиксированный аппаратный адрес.) И если периферийный регистр, на который он указывает, является аппаратным регистром только для чтения, тогда другой const обнаружит много ошибок во время компиляции, а не во время выполнения.

16-битный регистр микросхем только для чтения может выглядеть примерно так:

static const unsigned short *const peripheral = (unsigned short *)0xfe0000UL;

Тогда вы можете легко прочитать аппаратный регистр, не прибегая к языку ассемблера:

input_word = *peripheral;

5 голосов
/ 10 октября 2011

Ваш вопрос действительно больше о том, почему определять любую переменную как const, а не просто параметр const-указателя на функцию. Здесь применяются те же правила, что и при определении любой переменной как константы, если она является параметром для функции или переменной-члена или локальной переменной.

В вашем конкретном случае функционально это не имеет значения, как во многих других случаях, когда вы объявляете локальную переменную как const, но накладывает ограничение на невозможность ее изменения.

5 голосов
/ 10 октября 2011

Тот же вопрос можно задать по любому другому типу (не только по указателям):

/* Why is n const? */
const char *expand(const int n) {
    if (n == 1) return "one";
    if (n == 2) return "two";
    if (n == 3) return "three";
    return "many";
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...