ANSI C нужно ли использовать malloc() при создании структуры?

#c #memory-management #struct #malloc

#c #управление памятью #структура #malloc

Вопрос:

Допустим, у меня есть это struct в ANSI C:

 typedef struct _point
{
    float x;
    float y;
} Point;
  

и эта функция для создания этого struct :

 Point createpoint(float x, float y)
{
    Point p;
    p.x = x;
    p.y = y;
    return p; 
}
  

Это позволяет мне создавать struct с помощью этой функции, т.е.:

 int main()
{
    Point pointOne = createpoint(5, 6);  
    Point pointTwo = createpoint(10, 4);
    float distance = calculatedistancefunc(pointOne, pointTwo);

    /* ...other stuff */

    return 0;
}
  

Кто-то сказал мне, что этот код недействителен, потому struct что функция не получает malloc ‘d в createpoint(float x, float y) функции до того, как она будет возвращена, и что struct она будет удалена.
Однако, когда я использую my struct подобным образом, он, похоже, не удаляется.

Итак, мой вопрос: должен ли я malloc это struct делать и почему? / почему бы и нет?

Ответ №1:

Все, что вы делаете, совершенно правильно. Оператор —

 return p;
  

в функция возвращает копию локальной переменной p . Но если вам нужен тот же объект, который был создан в функции, тогда вам это нужно malloc . Однако вам понадобится free это позже.

 Point createpoint(float x, float y)
{
    Point p;
    p.x = x;
    p.y = y;
    return p; 
} // p is no longer valid from this point. So, what you are returning is a copy of it.
  

Но —

 Point* createpoint(float x, float y)
{
    Point *p = malloc(sizeof(Point));
    p->x = x;
    p->y = y;
    return p; 
}// Now you return the object that p is pointing to.
  

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

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

2. @mg30rg Почему вы должны передавать предварительно выделенный указатель на структуру, когда вы можете сделать это в функции и вернуть его?

3. @YassineHoussni — Потому что вы, скорее всего, будете читать свой код больше раз, чем вы его пишете, поэтому удобочитаемость кода важнее удобства кодирования. Если вы сохраняете выделение и освобождение памяти в одном блоке, утечку памяти будет сложнее, потому что ресурсы зарезервированы только на тот период, на который они необходимы, и каждое повторное выделение (т. Е.: malloc() / getmem() / new ) визуально сопряжено с запретом ( free() / freemem() / delete ) . Вы можете легко определить a new с no delete , но сможете ли вы определить a createpoint() без delete ? И будет ли программист обслуживания делать?

4. @mg30rg Да, вы правы, это очень хороший момент, спасибо

Ответ №2:

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

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

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

2. @Kernald — существуют проблемы с использованием памяти, проблемы с производительностью, проблемы с глубиной стека, но это не имеет отношения к прямому вопросу OP. Это допустимый C-код.

3. Спасибо всем за четкие ответы, но я, похоже, не понимаю, почему «передача по ссылке» более эффективна для памяти, чем «передача по значению». Я имею в виду, что оба способа хранят одинаковый объем данных в структуре?

4. @Marnixv.R. передача по значению по существу копирует значение. Это нормально для небольших переменных (например, целых чисел), но для структур, особенно больших, создание копии является расточительным. По ссылке означает, что вы ссылаетесь на существующую переменную, а не копируете ее в новую.

5. @littleadv: Модель вычислений C не содержит стека, а реализации C не обязательно помещают возвращаемые значения функции в стек. Для функций, возвращающих большие объекты, реализация может, в своих внутренних деталях, требовать от вызывающих абонентов передачи функции адреса места для ввода возвращаемого значения, и функция заполняет его там, а не в стеке.

Ответ №3:

C99 позволяет еще лучше создавать структуры в стеке.
Учитывая приведенную ниже структуру

 typedef struct
{
    float x;
    float y;
} Point;
  

вы можете инициализировать его в стиле конструктора C следующим утверждением:

 Point p = (Point){0.4, 0.5};
  

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

 int main()
{
    Point pointOne = (Point){5, 6};
    Point pointTwo = (Point){10, 4};
    float distance = calculatedistancefunc(pointOne, pointTwo);
    //...other stuff
    return 0;
}
  

Ответ №4:

 Point createpoint(float x, float y)
{
    Point p;
    p.x = x;
    p.y = y;
    return p; 
} /
  

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

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

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

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

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

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

3. вы также можете задать другой вопрос по этому поводу …!

Ответ №5:

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

 StructType BuildStruct(void)
{
  StructType it;
  it.this=4;
  it.that=23;
  return it;
}

StructType myStruct;
myStruct = BuildStruct();
  

вполне вероятно, что будет хотя бы одна операция копирования, если не две; оператору return it; может потребоваться скопировать из локальной переменной it во временную структуру, а присваиванию to myStruct может потребоваться скопировать из временного расположения в myStruct . На самом деле ни одна ситуация не требует двух операций копирования; для некоторых требуется одна (которая может быть выполнена вызывающим или вызываемым методом), а для некоторых не требуется ни одной, но необходимость копирования зависит от деталей как в вызывающем, так и в вызываемом методе.

Альтернативный дизайн будет:

 void BuildStruct(StructType *it)
{
  it->this=4;
  it->that=23;
}

StructType myStruct;
BuildStruct(amp;myStruct);
  

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