Почему функция может возвращать настройку массива с помощью malloc, а не одну настройку с помощью "int cat [3] = {0,0,0};" - PullRequest
3 голосов
/ 24 июля 2011

Почему я могу вернуть из функции настройку массива malloc:

int *dog = (int*)malloc(n * sizeof(int));

но не настройка массива

 int cat[3] = {0,0,0};

Массив "cat []" возвращается с предупреждением.

Спасибо всем за помощь

Ответы [ 7 ]

7 голосов
/ 24 июля 2011

Это вопрос области действия.

int cat[3]; // declares a local variable cat

Локальные переменные по сравнению с памятью malloc

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

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

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

Как избежать Malloc

Вам не нужно вызывать malloc внутри вашей функции (хотя это было бы нормально и целесообразно сделать так).

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

На самом деле это обычная модель.Однако, если вы сделаете это, вы обнаружите, что вам не нужно возвращать адрес, так как вы уже знаете адрес вне функции, которую вы вызываете.Из-за этого чаще возвращать значение, которое указывает на успех или неудачу процедуры, например, int, чем возвращать адрес соответствующих данных.

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

#include <stdio.h>             // include stdio for the printf function

int rainCats (int *cats);      // pass a pointer-to-int to function rainCats

int main (int argc, char *argv[]) {

    int cats[3];               // cats is the address to the first element

    int success;               // declare an int to store the success value
    success = rainCats(cats);  // pass the address to the function

    if (success == 0) {
        int i;
        for (i=0; i<3; i++) {
            printf("cat[%d] is %d \r", i, cats[i]);
            getchar();
        }
    }

    return 0;
}

int rainCats (int *cats) {
    int i;
    for (i=0; i<3; i++) {      // put a number in each element of the cats array
        cats[i] = i;
    }
    return 0;                  // return a zero to signify success
}

Почему это работает

Обратите внимание, что вам никогда не приходилось вызывать malloc здесь, потому что cats [3] было объявлено внутри главной функции.Локальные переменные в main будут уничтожены только при выходе из программы.Если программа не очень проста, malloc будет использоваться для создания и контроля продолжительности жизни структуры данных.

Также обратите внимание, что rainCats жестко задан для возврата 0. Внутри rainCats не происходит ничего, что могло бы привести к сбою.например, попытка доступа к файлу, сетевой запрос или другие выделения памяти.Более сложные программы имеют много причин сбоя, поэтому часто есть веская причина для возврата кода успеха.

4 голосов
/ 24 июля 2011

В работающей программе есть две ключевые части памяти: стек и куча . Стек также называется стеком вызовов .

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

Когда вы возвращаетесь из функции, все в стеке вытолкнуло выключено и пропало (и вскоре, когда вы сделаете еще несколько вызовов функций, вы перезапишите эту память, так что вы не захотите указываю на это!)

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

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

куча:

int *dog = (int*)malloc(n*sizeof(int*));

стек:

int cat[3] = {0,0,0};
4 голосов
/ 24 июля 2011

Поскольку int cat[3] = {0,0,0}; объявляет автоматическую переменную, которая существует только во время вызова функции.

Существует специальное "распределение" в C для инициируемых автоматических массивов char, так что строки в кавычках могутвозвращается, но не обобщается на другие типы массивов.

1 голос
/ 24 июля 2011

Это вопрос объекта время жизни , а не область действия, стек или куча. Хотя эти термины связаны с временем жизни объекта, они не эквивалентны времени жизни, и важно, чтобы вы возвращали время жизни объекта. Например, динамически распределенный объект имеет время жизни, которое простирается от выделения до освобождения. Время жизни локальной переменной может закончиться, когда закончится область действия переменной, но если она статическая, время ее жизни там не закончится.

Срок действия объекта, который был выделен с помощью malloc(), продолжается до тех пор, пока этот объект не будет освобожден с помощью функции free(). Поэтому, когда вы создаете объект, используя malloc(), вы можете законно вернуть указатель на этот объект, если вы не освободили его - он все еще будет жив, когда функция завершится. На самом деле вы должны позаботиться о том, чтобы что-то сделать с указателем, чтобы он где-то запомнился, или это приведет к утечке.

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

Теперь, если ваша локальная переменная static вместо автоматической, тогда ее время жизни выходит за пределы области действия, в которой оно находится (поэтому область действия не эквивалентна жизни). Поэтому, если у функции есть локальная статическая переменная, объект все еще будет жив, даже когда функция вернется, и было бы правомерно возвращать указатель на статический массив из вашей функции. Хотя это порождает целый ряд новых проблем, поскольку существует только один экземпляр этого объекта, поэтому его многократный возврат из функции может вызвать проблемы с совместным использованием данных (в основном это работает, только если данные не меняются после инициализации или если четкие правила, когда он может и не может измениться).

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

1 голос
/ 24 июля 2011

Вы не можете вернуть массив. Вы возвращаете указатель . Это не одно и то же.

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

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

1 голос
/ 24 июля 2011

Когда вы определяете переменную наподобие 'cat', компилятор присваивает ей адрес. Связь между именем и адресом действительна только в рамках определения. В случае автоматических переменных эта область является телом функции с момента определения и далее.

Автоматические переменные размещаются в стеке. Один и тот же адрес в стеке связан с разными переменными в разное время. Когда вы возвращаете массив, на самом деле возвращается адрес первого элемента массива. К сожалению, после возврата компилятор может и будет использовать это хранилище для совершенно не связанных целей. То, что вы увидите на уровне исходного кода, будет вашей загадочной измененной возвращаемой переменной без видимой причины.

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

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

Ничего из этого не имеет большого смысла, если вы не будете тщательно изучать работу стека. Удачи.

1 голос
/ 24 июля 2011

cat [] выделяется в стеке функции, которую вы вызываете, когда этот стек освобождается, память освобождается (когда функция возвращает стек, его следует считать освобожденным).

Если вы хотите заполнить массив целых чисел в вызывающем фрейме, передайте указатель на тот, которым вы управляете из вызывающего фрейма;

void somefunction() {
  int cats[3];
  findMyCats(cats);
}

void findMyCats(int *cats) {
  cats[0] = 0;
  cats[1] = 0;
  cats[2] = 0;
}

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

Одно значение работает, потому что оно копируется обратно в вызывающий кадр;

int findACat() {
  int cat = 3;
  return cat;
}

в findACat 3 копируется из findAtCat в вызывающий фрейм, поскольку это известное количество, которое компилятор может сделать за вас. Данные, на которые указывает указатель, скопировать невозможно, поскольку компилятор не знает, сколько копировать.

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