Как вернуть массив из функции в C?

#c #arrays #pointers

#c #массивы #указатели

Вопрос:

Большинство веб-сайтов говорят что-то вроде этого:

Программирование на C не позволяет возвращать весь массив в качестве аргумента функции. Однако вы можете вернуть указатель на массив, указав имя массива без индекса.

Я только начал с указателей, и, насколько я понимаю, переменная указателя — это переменная, которая хранит адрес памяти. Когда мы разыменовываем его с помощью * , мы получаем этот адрес в памяти и сохраняем сохраненное там значение. Кроме того, в случае массива указатель должен указывать на первый элемент.

Теперь, если наша функция возвращает указатель на первый элемент нашего массива, как в этом примере:

 int * myFunction() {
   .
   .
   .
}
  
  1. Что и как мы собираемся разыменовывать?
  2. Означает ли «функция, возвращающая указатель», что она возвращает адрес памяти, на который указывает указатель?

В этом случае,

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

  1. Что такое статическая переменная? [Я достаточно изучил, но не нашел ничего статистического]. Определение Википедии.:

В компьютерном программировании статическая переменная — это переменная, которая была выделена статически, так что ее время жизни или «экстент» распространяется на весь запуск программы.

На другом веб-сайте говорится,

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

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

Я действительно в замешательстве.

Комментарии:

1. Я продолжу и удалю тег C , который не имеет отношения к вашему вопросу. C != C.

2. «… чтобы вернуть весь массив в качестве аргумента функции» <- Поскольку вы изучаете C, позвольте мне быть здесь немного точным: передача чего-либо в качестве аргумента принципиально отличается от возврата чего-либо, поэтому, пожалуйста, не смешивайте терминологию.

3. Слишком много вопросов одновременно….

4. Я сильно сомневаюсь, что многие веб-сайты делают это заявление. Если бы я где-нибудь прочитал «вернуть x в функцию», я бы закрыл страницу и прочитал что-нибудь еще. Точная формулировка имеет слишком большое значение, чтобы тратить время на плохо написанные учебные пособия или тому подобное

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

Ответ №1:

Что и как мы собираемся разыменовывать?

Переменная-указатель, возвращаемая функцией.

Используя соответствующий оператор * , пример:

 int z = 5;
int* pointer_to_z = amp;z; // get memory address of z and store that in pointer_to_z
int can_i_have_my_z_back_please = *z; // check what value is at that memory address and store it, a copy is made here.
  

Означает ли «функция, возвращающая указатель», что она возвращает
адрес памяти, на который указывает указатель?

Он возвращает переменную-указатель, эта переменная содержит адрес значения в памяти. По сути, «указывать» на значение — это то же самое, что «иметь его адрес».

Что такое статическая переменная? [Я достаточно изучил, но не нашел ничего статистического].

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

 void hello()
{
  int x = 5;
} // x destroyed here..

void hello_static()
{
  static int x = 5;
} // x will only be destroyed at the "end" of the program
  

Это, в свою очередь, означает, что абсолютно безопасно возвращать указатель (адрес памяти) локальной статической переменной, поскольку статическая переменная все равно будет доступна :

 int* return_my_static()
{
  static int a = 5;
  return amp;a;
}

int main()
{
  int* pointer_to_static = return_my_static(); // get the memory address of the static
  printf("%i", *pointer_to_static); // print out the value by dereferencing
}
  

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

 int* return_local()
{
  int a = 5;
  return amp;a;
} // a is destroyed here.. oopsies

int main()
{
     int* pointer_to_local = return_local(); // get the memory address of the local.
     //local variable has been destroyed now and 'pointer_to_static' now points to garbage memory!
      printf("%i", *pointer_to_local); // Try to print out the value by dereferencing, this is undefined behaviour, anything can happen.
}
  

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

Комментарии:

1. Благодаря Gill, часть со статической переменной понятна. 🙂 У меня все еще есть вопрос. В третьем блоке кода в вашем ответе, внутри основной функции, ‘int* pointer_to_static = return_my_static(); printf(«%i», pointer_to_static);’ Здесь return_my_static вернет amp;a , адрес памяти, который хранится в pointer_to_static . Это делает pointer_to_static переменной указателя, верно? Тогда почему мы используем int ? Разве разыменование не требуется только в операторе printf?

2. Я сказал, почему мы используем int star ? Я не смог отредактировать комментарий.

3. @Narasimhan Все верно, я сделал это только для того, чтобы показать вам, что происходит.

Ответ №2:

Вопрос 3:

 int a;

void foo() {
   int b
   static int c;
}

int main() {
    foo();
}
  

При запуске программы память для a и c выделяется и остается выделенной до завершения программы. Таким образом, в любой момент времени есть точно на a и на c. Каждый раз, когда кто-либо (здесь main ) вызывает foo , b выделяется в стеке до тех пор, пока эта функция не вернется.

О цитате перед вопросом 3:

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

Вопрос 1:

Вы разыменовываете указатель, ставя звездочку впереди. Не имеет значения, на что указывает этот указатель. Если это массив, вы можете увеличивать или уменьшать указатель, чтобы перейти к индексу, который вы пытаетесь достичь, как в простом добавлении: *(p 4) получит доступ к 5-му элементу (потому что * (p 0) является первым, поэтому * (p 1)является вторым и так далее).

Вместо записи *(p 4) вы также можете написать p[4] .

Итак, предполагая, что ваша функция выглядит так:

 int * myFunction() {
    static int array[8];
    return array;
}
  

тогда оператор return вернет адрес массива, который в точности совпадает с адресом первого элемента массива.

итак, имея int * p = myFunction(); затем вы можете получить доступ к массиву с помощью интуитивно понятного синтаксиса p[0] = 42; …; p[7] = 23;

Вопрос 2:

Функция, которая возвращает указатель, — это функция, которая возвращает указатель. Указатель — это вещь, которая указывает на точку в памяти. Обычно это называется адресом памяти, но языку C все равно. Итак, на практике «функция, возвращающая указатель» означает, что она возвращает адрес памяти, на который указывает указатель, да.

Ответ №3:

Возвращаясь к началу:

Что такое статическая переменная?

Для объекта с static длительностью хранения выделено хранилище при первом запуске программы, и это хранилище удерживается до завершения программы. Объект имеет static продолжительность хранения, если:

  • его идентификатор объявляется в области видимости файла (вне тела любой функции); или
  • его идентификатор объявляется с static ключевым словом

Пример:

 #include <stdio.h>

int g_var;                      // static storage duration

void foo( void )
{
  static int s_var = 10;        // static storage duration
  int l_var = 10;               // auto storage duration

  printf( "g_var = %d, s_var = %d, l_var = %dn", g_var  , s_var  , l_var );
}
  

В этом фрагменте оба g_var и s_var имеют static длительность хранения; g_var потому что он был объявлен в области видимости файла, s_var потому что он был объявлен с static ключевым словом. В силу наличия static продолжительности хранения g_var неявно инициализируется значением 0. Обратите внимание, что s_var он инициализируется один раз при запуске программы — он не будет повторно инициализирован 10 при последующих вызовах foo . Таким образом, при каждом вызове foo вывод будет

 g_var = 0, s_var = 10, l_var = 10
g_var = 1, s_var = 11, l_var = 10
g_var = 2, s_var = 12, l_var = 10
...
  

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

Вот почему вы не можете сделать что-то вроде

 int * bar( void )
{
  int array[N];
  ...
  return array;
}
  

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

Теперь, один из способов обойти это — объявить массив как static :

 int * bar( void )
{
  static int array[N];
  ...
  return array;
}
  

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

Однако…

Это создает другие проблемы. Когда-либо создается только один экземпляр array , и он содержит последнюю вещь, записанную в него другим вызовом bar . Код больше не является повторным вводом; его нельзя безопасно прервать в середине выполнения, а затем вызвать другой функцией до завершения первого вызова. Создание static массива только для того, чтобы вы могли чисто вернуть на него указатель, обычно является неправильным ответом.

Либо передать целевой массив в качестве аргумента функции:

 void foo( int *array, size_t arraySize )
{
  ...
  array[i] = some_value;
  ...
}
  

или динамически выделить массив и вернуть указатель на него:

 int * bar( void )
{
  int *array = malloc( sizeof *array * N );
  if ( array )
  {
    // initialize array contents
  }
  return array;
}
  

Проблема в том, что кто-то другой отвечает за освобождение этой памяти, когда вы закончите.

Означает ли «функция, возвращающая указатель», что она возвращает адрес памяти, на который указывает указатель?

Функция возвращает значение указателя, который является адресом другого объекта. В приведенном выше коде bar возвращает значение выражения array , которое оказывается адресом первого элемента array .

Во втором bar приведенном выше случае возвращаемое значение эквивалентно amp;array[0] .

Что и как мы собираемся разыменовывать?

Вы можете разыменовать указатель двумя способами — с помощью оператора * разыменования или с помощью оператора [] подстрочного индекса.

Операция подстрочного a[i] индекса определяется как *(a i) — заданный адрес a , i элементы смещения (не байты) из a и разыменование результата. Таким образом, вы можете взять указатель, возвращенный из bar , и выполнить с ним следующее:

 int *p = bar();

printf( "p[0] = %dn", *p );
printf( "p[0] = %dn", *(p   0) );
printf( "p[0] = %dn", p[0] );
  

Итак, означает ли это, что массивы и указатели — это одно и то же? Нет. Массивы не являются указателями; однако в большинстве случаев выражение массива (т. Е. Выражение типа «Массив из N элементов T «) будет преобразовано («decay») в выражение указателя («указатель на T «).


  1. Очевидно, что область памяти, которая l_var была занята, все еще существует, поэтому значение указателя не становится внезапно мусором или чем-то подобным; однако эта область памяти теперь доступна для использования чем-то другим, и если вы попытаетесь прочитать или записать в это место, вы можете вызвать проблемы.