Я изучаю C по книге Программирование на C: современное Подход и у меня есть некоторые сомнения по поводу использования указателей. и ссылка на переменные, выходящие за рамки области видимости. Я собрал три примера, чтобы проиллюстрируйте мои сомнения.
Во-первых, у нас есть это:
char *f() {
char p[] = "Hi, I'm a string";
return p;
}
Насколько я понимаю, этот код проблематичен, поскольку p
— это переменная типа
массив chars
локально для области действия функции f()
. Когда я вернусь p
, я использую
имя массива как указатель на первый элемент, но поскольку переменная является только
действителен внутри области функции, когда я возвращаю p
, я получаю указатель на
недопустимая переменная (висячий указатель?). В результате, если я попытаюсь использовать p
вне функции я получаю ошибку сегментации.
Это верно?
Далее у меня есть это:
char *g() {
char *p = "Hi, I'm a string";
return p;
}
Это очень близко к предыдущему случаю, но вроде все в порядке, я могу получить доступ к строке
снаружи функции, и я не получаю предупреждения от компилятора (как это было в
предыдущий случай). Я не понимаю, почему это работает, p
объявлен как char
указатель, поэтому я предполагаю, что при его инициализации он указывает на первый символ в
строковый литерал, так же, как и в случае с массивом, это не так или это на самом деле
незапятнанное поведение, подходящее в моем конкретном контексте?
Наконец, у меня есть это:
char *h() {
char *p = malloc(17 * sizeof(char));
strcpy(p, "Hi, I'm a string");
return p;
}
Это как-то отличается от предыдущего примера? Я предполагаю, что это не так, я просто выделение памяти вручную. Кажется, это также позволяет мне получить доступ ко всей строке из вне функции, но у меня те же сомнения, что и в предыдущем примере.
Я протестировал три функции таким образом:
int main(int argc, char *argv[]) {
// This causes a segmentation fault
printf("%s\n", f());
// These two work ok
printf("%s\n", g());
printf("%s\n", h());
}
Можете ли вы помочь мне лучше понять, что здесь происходит? Заранее спасибо!
Как вы правильно указали, первая функция возвращает недопустимый указатель, поскольку массив, объявленный внутри функции и имеющий автоматическую продолжительность хранения, не будет активен после выхода из функции. Разыменование такого указателя приводит к неопределенному поведению.
Обратите внимание, что инициализация массива строковым литералом означает, что символы строкового литерала, включая его завершающий нулевой символ '\0'
, копируются в объём памяти, выделенный для массива.
Во второй функции возвращается указатель на первый символ строкового литерала. Строковые литералы имеют статическую продолжительность хранения. Таким образом, используемый строковый литерал будет активен после выхода из функции, и в результате функция вернет действительный указатель.
Имейте в виду, что хотя в строковых литералах C есть типы непостоянных массивов символов, вы не можете изменять строковые литералы. Поэтому было бы лучше объявить вторую функцию следующим образом:
const char *g( void ) {
const char *p = "Hi, I'm a string";
return p;
}
В третьей функции память выделяется динамически. Он не будет освобожден до тех пор, пока не будет вызвана функция free. Таким образом, возвращенный указатель действителен.
Чтобы освободить выделенную память, вы можете написать в main
char *p = h();
if ( p != NULL ) printf("%s\n", p);
free( p );
Сама функция h
должна быть определена следующим образом
char *h( void ) {
char *p = malloc(17 * sizeof(char));
if ( p != NULL ) strcpy(p, "Hi, I'm a string");
return p;
}
Спасибо! Это имеет большой смысл. Таким образом, основные различия между первыми двумя случаями заключаются в следующем: - В 1. выполняется неявная операция копирования строкового литерала в массив (который является локальной переменной). Поскольку указатель указывает на (локальный) массив, это недопустимый указатель вне функции. - В версии 2. нет локальной строки, функция напрямую возвращает указатель на строковый литерал, который всегда доступен. В версии 3, поскольку я выделяю память вручную, выход за пределы области действия не освобождает ее автоматически. Это правильно?
@видео 1. да. 2. да. 3. да, но в любом случае автоматическое освобождение выделенной памяти при выходе из функции не имело бы никакого смысла.
Существует бесчисленное множество примеров, когда эти вопросы уже задавались на SO.