Почему я могу вернуть `int` после функции, но не` char * `? - PullRequest
0 голосов
/ 24 декабря 2018

Я новичок в C. Я расширил вопрос из предыдущего вопроса: Странное поведение при возврате "строки" с C (Кстати, спасибо всем, кто ответил или прокомментировал этот вопрос).)

Довольно прямо:

Почему это может работать:

#include <stdio.h>

int func() {
    int i = 5;
    return i;
}

int main() {
    printf("%d",func());
}

Но не это:

#include <stdio.h>

char * func() {
    char c[] = "Hey there!";
    return c;
}

int main() {
    printf("%s",func());
}

Отпредыдущий вопрос, логически int i тоже не должно существовать, потому что функция имеет return ed, но почему он все еще может быть возвращен, в то время как char c[] не может?

(Кажется, он дублируется из« Указатели и объем памяти », но я хотел бы узнать больше о том, в чем разница между возвращением int и char *.)

Ответы [ 6 ]

0 голосов
/ 24 декабря 2018

Возвращаемое значение функции возвращается copy .В первом примере вы получаете копию целочисленной переменной из функции.Во втором вы получите копию указателя на символ , а не копию строки.

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

Дело в том, что возвращается указатель, а не строка;в Си строки (и, в более общем случае, массивы) не являются типами данных первого класса.

В зависимости от ваших потребностей существует несколько способов возврата строковых данных;например, допустимо следующее:

char* func() 
{
    static char c[] = "Hey there!";
    return c;
}

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

Другой альтернативой является встраивание строки в структуру, которая является типом данных первого класса:

typedef struct
{
    char content[256] ;
} sString ;

sString func() 
{
    sString c = {"Hey there!"};
    return c;
}

Или условно copy данные в буфер вызывающего абонента:

char* func( char* buffer ) 
{
    char c[] = "Hey there!";

    strcpy( buffer, c ) ;

    return buffer ;
}

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

0 голосов
/ 24 декабря 2018

В первом примере вы возвращаете int, а во втором вы возвращаете указатель на символ.Они оба возвращаются точно одинаковым образом, это просто вопрос понимания стека и того, как возвращаются значения.

Даже если i было объявлено в функции и размещено в стеке, когда функциявозвращает, возвращает значение i (которое в основном копируется, поэтому, когда i падает со стека, значение i все еще возвращается.)

Это то же самое, что происходит сchar * во втором примере.Он по-прежнему будет указателем на символ и возвращает «скопированное» значение c.Однако, поскольку он был размещен в стеке, адрес, на который он указывает, фактически недействителен.Само значение указателя не изменилось, но имеет то, на что оно указывает.

Вы должны будете динамически распределить его, чтобы избежать этой ситуации.

0 голосов
/ 24 декабря 2018

Вы пытаетесь вернуть указатель на локальный массив, что очень плохо.Если вы хотите вернуть указатель на массив, выделите его динамически, используя malloc внутри вашего func ();

Затем вы должны вызвать free () на стороне вызывающей стороны, чтобы освободить память, выделенную вам, когда она вам больше не нужна

0 голосов
/ 24 декабря 2018

В первом случае вы возвращаете значение int 5 из функции.Затем вы можете распечатать это значение.

Однако во втором случае вы возвращаете значение типа char *.Это значение указывает на массив, локальный для функции func.После того, как эта функция возвращает массив, выходит из области видимости, поэтому указатель указывает на недопустимую память.

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

0 голосов
/ 24 декабря 2018

Проблема не возвращает char *, она возвращает что-то, что выделено в стеке.

Если вы выделите память для своей строки вместо того, чтобы указывать на стек функций, проблем не будет.Примерно так:

char * func() {
    char c[] = "Hey there!";
    return strdup(c);
}

int main() {
    char* str = func();
    printf("%s", str);
    free(str);
}

Важно отметить, что в обоих случаях вы копируете значение, и в обоих случаях копируемое значение является правильным, , но значение скопированного значения отличается ,

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

На основе предложенийв комментарии я решил добавить еще одну лучшую практику распределения памяти для этого кода:

#define NULL (void*)0

int func(char *buf, int len) {
    char c[] = "Hey there!";
    int size = strlen(c) + 1;

    if (len >= size) {
        strcpy(buf, c);
    }

    return size;
}

int main() {
    int size = func(NULL, 0);
    char *buf = calloc(size, sizeof(*buf));
    func(buf, size);
    printf("%s", buf);
    free(buf);
    return 0;
}

Подобный подход используется во многих функциях Windows API.Этот подход лучше, потому что владелец указателя более очевиден (main здесь).

0 голосов
/ 24 декабря 2018

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...