В чем разница между возвратом char * и char [] из функции?

Почему первая функция возвращает строку "Hello, World", но вторая функция ничего не возвращает. Я думал, что возвращаемое значение обеих функций будет undefined, поскольку они возвращают данные, выходящие за рамки.

#include <stdio.h>
// This successfully returns "Hello, World"
char* function1()
{
    char* string = "Hello, World!";
    return string;
}
// This returns nothing
char* function2()
{
    char string[] = "Hello, World!";
    return string; 
}

int main()
{
    char* foo1 = function1();
    printf("%s\n", foo1); // Prints "Hello, World"
    printf("------------\n");
    char* foo2 = function2(); // Prints nothing
    printf("%s\n", foo2);
    return 0;
}

Ответ 1

вторая функция ничего не возвращает

Массив string во второй функции:

char string[] = "Hello, World!";

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

В то время как string в первой функции:

char* string = "Hello, World!";

указывает на литеральную строку, которая имеет статическую продолжительность хранения. Это означает, что строка возвращается после возврата из функции. То, что вы возвращаете из функции, является указателем на эту литеральную строку.

Ответ 2

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

Что делает первая функция (function1), возвращает указатель на первый элемент такого массива.

Со второй функцией (function2) все немного отличается. Здесь переменная string является локальной переменной внутри функции. Таким образом, он выйдет за рамки и перестанет существовать после возвращения функции. С помощью этой функции вы возвращаете указатель на первый элемент этого массива, но этот указатель немедленно станет недействительным, так как он укажет на то, что больше не существует. Развертывание его (что происходит, когда вы передаете его на printf) приведет к undefined поведение.

Ответ 3

Очень важно помнить, что когда кодирование на языках C или других стеков основано на том, что когда функция возвращается, она (и все ее локальное хранилище) исчезла. Это означает, что если вы хотите, чтобы кто-то еще мог видеть результаты ваших трудных действий, вы должны положить его где-нибудь, что будет существовать после того, как ваш метод перестанет работать, и для этого вам нужно понять где C хранит материал и как.

Вероятно, вы уже знаете, как работает массив в C. Это просто адрес памяти, который увеличивается на размер объекта, и вы, вероятно, также знаете, что C не выполняет проверку границ, поэтому, если вы хотите получить доступ к 11-му элементу из десяти элементов массива, никто вас не остановит, и пока вы ничего не пытаетесь написать, никакого вреда не будет. Возможно, вы не знаете, что C расширяет эту идею до того, как она использует функции и переменные. Функция - это всего лишь область памяти в стеке, которая загружается по требованию, а хранилище для ее переменных просто смещается из этого местоположения. Ваша функция вернула указатель на локальную переменную, а именно адрес места в стеке, в котором содержится "H" "Hello World\n\0", но когда вы вызывали другую функцию (метод печати), в которой была память повторно используемый методом печати, чтобы сделать то, что ему нужно. Вы можете видеть это достаточно легко (НЕ ДЕЛАЙТЕ ЭТО В ПРОДУКЦИОННОМ КОДЕЛЕ!!!)

char* foo2 = function2(); // Prints nothing
ch = foo2[0];  // Do not do this in live code!
printf("%s\n", foo2);  // stack used by foo2 now used by print()
printf("ch is %c\n", ch);  // will have the value 'H'!

Ответ 4

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

Нет. Это не так.

В функции function1 вы возвращаете указатель на строковый литерал. Возвращающий указатель на строковый литерал прекрасен, потому что строковые литералы имеют статическую продолжительность хранения. Но это не так с автоматической локальной переменной.

В функции function2 массив string является автоматической локальной переменной и утверждение

return string; 

возвращает указатель на автоматическую локальную переменную. После возвращения функции переменная string больше не будет существовать. Выделение возвращаемого указателя приведет к поведению undefined.

Ответ 5

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

Обе функции возвращают указатель. Важна область действия референта.

В function1 референтом является строковый литерал "Hello, World!", который имеет статическую продолжительность хранения. string - это локальная переменная, которая указывает на эту строку, и концептуально возвращается копия этого указателя (на практике компилятор избегает ненужного копирования значения).

В function2 концептуально референтом является локальный массив string, который был автоматически установлен (во время компиляции), чтобы быть достаточно большим, чтобы содержать строковый литерал (включая нулевой терминатор, конечно), и был инициализируется копией строки. Функция вернет указатель на этот массив, за исключением того, что массив имеет автоматическую продолжительность хранения и, следовательно, больше не существует после выхода из функции (это действительно "выходит за рамки", в более привычной терминологии). Поскольку это поведение undefined, компилятор может на практике делать всевозможные вещи.

Означает ли это, что все char* являются статическими?

Опять же, вам нужно различать указатель и референт. Указатели указывают на данные; они сами не "содержат" данные.

Вы достигли точки, где вы должны правильно изучить, какие массивы и указатели на самом деле находятся на C - к сожалению, это немного беспорядок. Лучшая рекомендация, которую я могу предложить, - это this, в формате Q & A.

Ответ 6

"Hello, World!" - строковый литерал, который имеет статическую продолжительность хранения, поэтому проблема в другом месте. Ваша первая функция возвращает значение string, что отлично. Вторая функция, однако, возвращает адрес локальной переменной (string совпадает с &string[0]), что приводит к поведению undefined. Ваш второй оператор printf может ничего не печатать, или "Hello, World!", Или что-то еще. На моей машине программа просто получает ошибку сегментации.

Всегда смотрите сообщения, выводимые на компилятор. Для вашего примера gcc дает:

file.c:12:12: warning: function returns address of local variable [-Wreturn-local-addr]
    return string; 
           ^

что в значительной степени самоочевидно.