Как я должен определить/объявить строковые константы
Я всегда использовал строковые константы в C как один из следующих
char *filename = "foo.txt";
const char *s = "bar"; /* preferably this or the next one */
const char * const s3 = "baz":
Но, прочитав это, теперь я задаюсь вопросом, должен ли я объявить свои строковые константы как
const char s4[] = "bux";
?
Обратите внимание, что связанный вопрос, предложенный в качестве дубликата, отличается, потому что этот вопрос конкретно касается константных строк. Я знаю, как разные типы и как они хранятся. Версия массива в этом вопросе не const
-qualified. Это был простой вопрос относительно того, должен ли я использовать константный массив для константных строк по сравнению с версией указателя, которую я использовал. Ответы здесь ответили на мой вопрос, когда два дня поиска по SO и Google не дали точного ответа. Благодаря этим ответам я узнал, что компилятор может делать особые вещи, когда массив помечен как const
, и действительно есть (по крайней мере, один) случай, когда я теперь буду использовать версию массива.
Ответы
Ответ 1
Указатель и массивы разные. Определение строковых констант как указателей или массивов подходит для разных целей.
Когда вы определяете глобальную строковую константу, которая не подлежит изменению, я бы рекомендовал сделать ее константным массивом:
const char product_name[] = "The program version 3";
Определив его как const char *product_name = "The program version 3";
фактически определяет 2 объекта: саму строковую константу, которая будет находиться в постоянном сегменте, и указатель, который можно изменить, чтобы он указывал на другую строку или устанавливал NULL
.
И наоборот, определение строковой константы как локальной переменной было бы лучше сделать в виде локальной переменной-указателя типа const char *
, инициализированной адресом строковой константы:
int main() {
const char *s1 = "world";
printf("Hello %s\n", s1);
return 0;
}
Если вы определите его как массив, в зависимости от компилятора и использования внутри функции, код освободит пространство для массива в стеке и инициализирует его, скопировав в него строковую константу, что является более дорогостоящей операцией для длинных строк.
Также обратите внимание, что const char const *s3 = "baz";
является избыточной формой const char *s3 = "baz";
, Это отличается от const char * const s3 = "baz";
который определяет постоянный указатель на постоянный массив символов.
Наконец, строковые константы являются неизменяемыми и должны иметь тип const char []
. Стандарт C специально позволяет программистам сохранять свои адреса в неконстантных указателях, как в char *s2 = "hello";
чтобы избежать выдачи предупреждений для устаревшего кода. В новом коде настоятельно рекомендуется всегда использовать указатели const char *
для манипулирования строковыми константами. Это может заставить вас объявить аргументы функции как const char *
когда функция не изменяет содержимое строки. Этот процесс известен как консистенция и позволяет избежать мелких ошибок.
Обратите внимание, что некоторые функции нарушают это распространение const
: strchr()
не изменяет полученную строку, объявленную как const char *
, но возвращает char *
. Таким образом, можно сохранить указатель на строковую константу в обычном указателе char *
следующим образом:
char *p = strchr("Hello World\n", 'H');
Эта проблема решена в C++ путем перегрузки. Программисты C должны иметь дело с этим как с недостатком. Еще более раздражающая ситуация - это strtol()
где strtol()
адрес char *
и требуется преобразование для сохранения правильной константности.
Ответ 2
В связанной статье рассматривается небольшая искусственная ситуация, и демонстрируемая разница исчезает, если вставить const
после *
в const char *ptr = "Lorum ipsum";
(протестировано в Apple LLVM 10.0.0 с clang-1000.11.45.5).
Тот факт, что компилятор должен был загружать ptr
возник полностью из-за того, что он мог быть изменен в каком-то другом модуле, невидимом для компилятора. Создание указателя const
устраняет это, и компилятор может подготовить адрес строки напрямую, без загрузки указателя.
Если вы собираетесь объявить указатель на строку и никогда не менять указатель, то объявите его как static const char * const ptr = "string";
и компилятор может с радостью предоставить адрес строки всякий раз, когда используется значение ptr
. На самом деле ему не нужно загружать содержимое ptr
из памяти, поскольку оно никогда не может измениться и будет известно, что оно указывает на то место, где компилятор решит сохранить строку. Это то же самое, что static const char array[] = "string";
- когда бы ни требовался адрес массива, компилятор может предоставить его из своего знания о том, где он решил хранить массив.
Кроме того, с помощью static
спецификатора ptr
не может быть известен за пределами модуля перевода (файл, который компилируется), поэтому компилятор может удалить его во время оптимизации (если вы не взяли его адрес, возможно, при передаче его другой подпрограмме вне переводческий блок). В результате не должно быть различий между методом указателя и методом массива.
Практическое правило. Расскажите компилятору столько, сколько вы знаете о вещах: если он никогда не изменится, отметьте его const
. Если он является локальным для текущего модуля, отметьте его как static
. Чем больше информации имеет компилятор, тем больше он может оптимизировать.
Ответ 3
С точки зрения производительности, это довольно небольшая оптимизация, которая имеет смысл для низкоуровневого кода, который должен работать с минимально возможной задержкой.
Однако я бы сказал, что const char s3[] = "bux";
лучше с семантической точки зрения, потому что тип правой стороны ближе к типу левой стороны. По этой причине я думаю, что имеет смысл объявить строковые константы с синтаксисом массива.