Я заметил, что это - общая идиома в C для принятия не-malloc
указатель редактора как второй аргумент вместо того, чтобы возвратить указатель. Пример:
/*function prototype*/
void create_node(node_t* new_node, void* _val, int _type);
/* implementation */
node_t* n;
create_node(n, &someint, INT)
Вместо
/* function prototype */
node_t* create_node(void* _val, int _type)
/* implementation */
node_t* n = create_node(&someint, INT)
Каковы преимущества и/или недостатки обоих подходов?
Спасибо!
РЕДАКТИРОВАНИЕ Спасибо всем за Ваши ответы. Мотивации для выбора 1 очень ясны мне теперь (и я должен указать, что аргументом указателя в пользу выбора 1 должен быть malloc'd вопреки тому, что я первоначально думал).
Принятие указателя (за который вызывающий отвечает за неправильную привязку или нет) на заполняемую память дает серьезные преимущества в гибкости по сравнению с возвратом указателя (обязательно с ошибкой) . В частности, если вызывающий знает, что ему нужно использовать все, что было возвращено только в рамках определенной функции, он может передать адрес структуры или массива, выделенного стеком; если он знает, что повторный вход не требуется, он может передать адрес структуры или массива static
- в любом случае пара malloc / free сохраняется, и такая экономия увеличивается! - )
В этом нет особого смысла. Указатели в C передаются по значению, как и другие объекты, разница заключается в значении. В случае указателей значение - это адрес памяти, который передается функции. Однако вы по-прежнему дублируете значение, поэтому, когда вы malloc
, вы будете изменять значение указателя внутри вашей функции, а не снаружи.
void create_node(node_t* new_node, void* _val, int _type) {
new_node = malloc(sizeof(node_t) * SIZE);
// `new_node` points to the new location, but `n` doesn't.
...
}
int main() {
...
node_t* n = NULL;
create_node(n, &someint, INT);
// `n` is still NULL
...
}
Есть три способа избежать этого. Первый - это, как вы упомянули, возврат нового указателя из функции. Второй - взять указатель на указатель и передать его по ссылке:
void create_node(node_t** new_node, void* _val, int _type) {
*new_node = malloc(sizeof(node_t) * SIZE);
// `*new_node` points to the new location, as does `n`.
...
}
int main() {
...
node_t* n = NULL;
create_node(&n, &someint, INT);
// `n` points to the new location
...
}
Третий - просто malloc
n
вне вызова функции:
int main() {
...
node_t* n = malloc(sizeof(node_t) * SIZE);
create_node(n, &someint, INT);
...
}
Я обычно предпочитаю получать указатели (инициализированные свойства) в качестве аргументов функции, а не для возврата указателя на область памяти, которая была заблокирована внутри функции. При таком подходе вы четко заявляете, что ответственность за управление памятью лежит на стороне пользователя.
Возврат указателей обычно приводит к утечкам памяти, так как легче забыть free () ваши указатели, если вы не изменили их malloc ().
Обычно я не делаю фиксированного выбора, я аккуратно помещаю его в отдельную библиотеку и предлагаю лучшее из обоих миров.
void node_init (node_t *n);
void node_term (node_t *n);
node_t *node_create ()
{
node_t *n = malloc(sizeof *n);
/* boilerplate error handling for malloc returning NULL goes here */
node_init(n);
return n;
}
void node_destroy (node_t *n)
{
node_term(n);
free(n);
}
Для каждого malloc должен быть свободный, таким образом, для каждого init должен быть термин, а для каждого создания должно быть уничтожение. По мере того, как ваши объекты становятся более сложными, вы обнаружите, что начинаете их вкладывать. Некоторые объекты более высокого уровня могут использовать список node_t для внутреннего управления данными. Перед освобождением этого объекта необходимо сначала освободить список. _init и _term заботятся об этом, полностью скрывая детали реализации.
Могут быть приняты решения о дальнейших деталях, например destroy может взять node_t ** n и установить * n в NULL после его освобождения.
Лично мне нравится возвращать данные, используя параметры ссылки или указателя, и использовать функцию return для возврата кодов ошибок.
1) Как указал Самир, код неверен, указатель передается по значению, вам нужно **
2) Функция по сути является конструктором, поэтому имеет смысл чтобы он и выделил память, и инициализировал структуру данных. Чистый код C почти всегда объектно-ориентирован, как с конструкторами и деструкторами.
3) Ваша функция недействительна, но она должна возвращать int, чтобы возвращать ошибки. Будет как минимум 2, возможно, 3 возможных состояния ошибки: malloc может завершиться ошибкой, аргумент типа может быть недопустимым и, возможно, значение может быть вне допустимого диапазона.