Когда выделять память на char *
Я немного смущен, когда выделяю память на char * и когда указывать ее на строку const.
Да, я понимаю, что если я хочу изменить строку, мне нужно выделить ее.
Но в тех случаях, когда я не хочу изменять строку, на которую я указываю, и мне просто нужно передать значение, я должен просто сделать это ниже? Каковы недостатки следующих шагов по сравнению с распределением памяти с помощью malloc
?
char *str = NULL;
str = "This is a test";
str = "Now I am pointing here";
Ответы
Ответ 1
Повторите свой пример с предупреждающим знаком -Wwrite-strings
, вы увидите предупреждение:
warning: initialization discards 'const' qualifier from pointer target type
Это потому, что тип "Это тест" const char *
, а не char *
. Таким образом, вы теряете информацию о constost, когда вы назначаете литеральный адрес указателю.
По историческим причинам компиляторы позволят вам хранить строковые литералы, которые являются константами в неконстантных переменных.
Это, однако, плохое поведение, и я предлагаю вам использовать -Wwrite-strings
все время.
Если вы хотите доказать это сами, попробуйте изменить строку:
char *str = "foo";
str[0] = 'a';
Это поведение программы undefined, но во многих системах может наблюдаться ошибка сегментации.
Запустив этот пример с Valgrind, вы увидите следующее:
Process terminating with default action of signal 11 (SIGSEGV)
Bad permissions for mapped region at address 0x4005E4
Проблема в том, что двоичный код, сгенерированный вашим компилятором, сохранит строковые литералы в ячейке памяти которая доступна только для чтения. Пытаясь записать в нем, вы вызываете ошибку сегментации.
Важно понимать, что вы имеете дело с двумя разными системами:
-
Система ввода C, которая поможет вам написать правильный код и может быть легко "отключена" (путем кастингов и т.д.).
-
Разрешения страницы памяти ядра, которые предназначены для защиты вашей системы и которые всегда должны выполняться.
Опять же, по историческим причинам, это точка, где 1. и 2. не согласны. Или, чтобы быть более понятным, 1. намного более разрешительный, чем 2. (в результате ваша программа была убита ядром).
Итак, не комментируйте, строковые литералы, которые вы объявляете, действительно постоянны, и вы ничего не можете с этим поделать!
Учитывая ваш указатель str
чтение и запись в порядке.
Однако, чтобы написать правильный код, он должен быть const char *
, а не char *
. При следующем изменении ваш пример является допустимым фрагментом C:
const char *str = "some string";
str = "some other string";
(const char *
указатель на строку const)
В этом случае компилятор не выдаёт никаких предупреждений. То, что вы пишете и что будет в памяти после выполнения кода, будет соответствовать.
Примечание. Указатель const для строки const, являющийся const char *const
:
const char *const str = "foo";
Эмпирическое правило: всегда должно быть как можно более постоянным.
Если вам нужно изменить строку, используйте динамическое распределение (malloc() или лучше, некоторые функции управления строкой более высокого уровня, такие как strdup и т.д. из libc), если вам не нужно, используйте строковый литерал.
Ответ 2
Если вы знаете, что str
всегда будет доступен только для чтения, почему бы не объявить его как таковой?
char const * str = NULL;
/* OR */
const char * str = NULL;
Ну, на самом деле есть одна причина, почему это может быть сложно - когда вы передаете строку в функцию только для чтения, которая не объявляет себя как таковую. Предположим, вы используете внешнюю библиотеку, объявляющую эту функцию:
int countLettersInString(char c, char * str);
/* returns the number of times `c` occurs in `str`, or -1 if `str` is NULL. */
Эта функция хорошо документирована, и вы знаете, что она не будет пытаться изменить строку str
, но если вы вызываете ее с постоянной строкой, ваш компилятор может дать вам предупреждение! Вы знаете, что в этом нет ничего опасного, но ваш компилятор этого не делает.
Почему? Поскольку, что касается компилятора, возможно, эта функция пытается изменить содержимое строки, что приведет к сбою вашей программы. Возможно, вы очень сильно полагаетесь на эту библиотеку, и есть много функций, которые все ведут себя так. Тогда, возможно, проще не объявлять строку как const
в первую очередь - но тогда все зависит от вас, чтобы вы не пытались ее модифицировать.
С другой стороны, если вы пишете функцию countLettersInString
, просто убедитесь, что компилятор знает, что вы не будете изменять строку, объявив ее с помощью const
:
int countLettersInString(char c, char const * str);
Таким образом, он будет принимать как постоянные, так и непостоянные строки без проблем.
Ответ 3
Одним из недостатков использования строковых литералов является то, что они имеют ограничения по длине.
Поэтому вы должны иметь в виду документ ISO/IEC: 9899
(акцент мой)
5.2.4.1 Пределы перевода
1 Реализация должна иметь возможность переводить и выполнять хотя бы одну программу, содержащую хотя бы один экземпляр каждого из следующих ограничений:
[...]
- 4095 символов в литеральной строке символа или в широком строковом литерале (после конкатенации)
Итак, если ваш постоянный текст превышает этот счет (что может быть несколько раз, особенно если вы пишете динамический веб-сервер в C) вам запрещено использовать строковый литерал, если вы хотите оставаться независимым от системы.
Ответ 4
В вашем коде нет проблем, если вы не планируете изменять содержимое этой строки. Кроме того, память для таких строковых литералов останется на весь срок службы программы. Память, выделяемая malloc
, чтение-запись, поэтому вы можете манипулировать содержимым этой памяти.
Ответ 5
Если у вас есть строковый литерал, который вы не хотите изменять, что вы делаете, это нормально:
char *str = NULL;
str = "This is a test";
str = "Now I am pointing here";
Здесь str
указатель имеет память, на которую указывает. Во второй строке вы пишете в эту память "This is a test"
, а затем снова в 3 строках, которые вы пишете в этой памяти "Now I am pointing here"
. Это законно в C.
Вы можете найти это немного противоречивым, но вы не можете изменить строку, что-то вроде этого -
str[0]='X' // will give a problem.
Однако, если вы хотите изменить его, используйте его как буфер для хранения строки ввода и т.д., используйте malloc
:
char *str=malloc(BUFSIZE); // BUFSIZE size what you want to allocate
free(str); // freeing memory
Используйте malloc()
, когда вы не знаете объем памяти, необходимый во время компиляции.
Ответ 6
В К сожалению, это законно, но любая попытка изменить строковый литерал через указатель приведет к поведению undefined.
Скажем
str[0] = 'Y'; //No compiler error, undefined behavior
Ответ 7
Он будет работать нормально, но вы можете получить предупреждение от компилятора, потому что вы указываете на константу.
P.S.: Он будет работать только тогда, когда вы его не модифицируете. Поэтому единственным недостатком использования malloc
является то, что вы не сможете его изменить.