Указатели C: функция возвращает указатель, определенный в ее теле

Я изучаю C по книге Программирование на C: современное Подход и у меня есть некоторые сомнения по поводу использования указателей. и ссылка на переменные, выходящие за рамки области видимости. Я собрал три примера, чтобы проиллюстрируйте мои сомнения.

  1. Во-первых, у нас есть это:

    char *f() {
        char p[] = "Hi, I'm a string";
        return p;
    }
    

    Насколько я понимаю, этот код проблематичен, поскольку p — это переменная типа массив chars локально для области действия функции f(). Когда я вернусь p, я использую имя массива как указатель на первый элемент, но поскольку переменная является только действителен внутри области функции, когда я возвращаю p, я получаю указатель на недопустимая переменная (висячий указатель?). В результате, если я попытаюсь использовать p вне функции я получаю ошибку сегментации.

    Это верно?

  2. Далее у меня есть это:

    char *g() {
        char *p = "Hi, I'm a string";
        return p;
    }
    

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

  3. Наконец, у меня есть это:

    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());
}

Можете ли вы помочь мне лучше понять, что здесь происходит? Заранее спасибо!

Существует бесчисленное множество примеров, когда эти вопросы уже задавались на SO.

Lundin 05.07.2024 13:13
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
77
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Обратите внимание, что инициализация массива строковым литералом означает, что символы строкового литерала, включая его завершающий нулевой символ '\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, поскольку я выделяю память вручную, выход за пределы области действия не освобождает ее автоматически. Это правильно?

videbar 05.07.2024 13:20

@видео 1. да. 2. да. 3. да, но в любом случае автоматическое освобождение выделенной памяти при выходе из функции не имело бы никакого смысла.

Jabberwocky 05.07.2024 13:55

Другие вопросы по теме