указатель как второй аргумент вместо того, чтобы возвратить указатель?

Я заметил, что это - общая идиома в 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 вопреки тому, что я первоначально думал).

7
задан Kris 31 March 2010 в 14:25
поделиться

6 ответов

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

16
ответ дан 6 December 2019 в 07:05
поделиться

В этом нет особого смысла. Указатели в 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);
    ...
}
6
ответ дан 6 December 2019 в 07:05
поделиться

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

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

2
ответ дан 6 December 2019 в 07:05
поделиться

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

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 после его освобождения.

1
ответ дан 6 December 2019 в 07:05
поделиться

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

0
ответ дан 6 December 2019 в 07:05
поделиться

1) Как указал Самир, код неверен, указатель передается по значению, вам нужно **

2) Функция по сути является конструктором, поэтому имеет смысл чтобы он и выделил память, и инициализировал структуру данных. Чистый код C почти всегда объектно-ориентирован, как с конструкторами и деструкторами.

3) Ваша функция недействительна, но она должна возвращать int, чтобы возвращать ошибки. Будет как минимум 2, возможно, 3 возможных состояния ошибки: malloc может завершиться ошибкой, аргумент типа может быть недопустимым и, возможно, значение может быть вне допустимого диапазона.

0
ответ дан 6 December 2019 в 07:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: