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

Этот вопрос предназначен для использования в качестве канонического дубликата для этого FAQ:

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

Вот MCVE:

#include <stdlib.h>
#include <stdio.h>

void create_array (int* data, int size)
{
  data = malloc(sizeof(*data) * size);
  for(int i=0; i<size; i++)
  {
    data[i] = i;
  }

  print_array(data, size);
}

void print_array (int* data, int size)
{
  for(int i=0; i<size; i++)
  {
    printf("%d ", data[i]);
  }
  printf("\n");
}

int main (void)
{
  int* data;
  const int size = 5;

  create_array(data, size);
  print_array(data, size);  // crash here

  free(data);
}

Всякий раз, когда print_array вызывается из функции create_array, я получаю ожидаемый результат 0 1 2 3 4, но когда я вызываю его из main, я получаю сбой программы.

Что является причиной этого?

Ответ 1

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


Рассмотрим этот простой пример:

void func (int x)
{
  x = 1;
  printf("%d", x);
}

...
int a;
func(a);
printf("%d", a); // bad, undefined behavior - the program might crash or print garbage

Здесь копия переменной a хранится локально внутри функции в качестве параметра x. Это называется передачей по значению.

При изменении x изменяется только локальная переменная. Переменная a в вызывающем абоненте остается неизменной, и поскольку a не инициализируется, она будет содержать "мусор" и не может быть надежно использована.


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

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


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

int* create_array (int size)
{
  int* data = malloc(sizeof(*data) * size);
  for(int i=0; i<size; i++)
  {
    data[i] = i;
  }

  print_array(data, size);

  return data;
}

int main (void)
{
  int* data;
  const int size = 5;

  data = create_array(size);
  print_array(data, size);
}

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

void create_array (int** data, int size)
{
  int* tmp = malloc(sizeof(*tmp) * size);
  for(int i=0; i<size; i++)
  {
    tmp[i] = i;
  }

  *data = tmp;      
  print_array(*data, size);
}

int main (void)
{
  int* data;
  const int size = 5;

  create_array(&data, size);
  print_array(data, size);
}

Любая форма в порядке.