указатель в качестве второго аргумента вместо возврата указателя? - PullRequest
7 голосов
/ 26 марта 2010

Я заметил, что в C распространенной идиомой является принятие не malloc ed указателя в качестве второго аргумента вместо возврата указателя. Пример:

/*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 ]

16 голосов
/ 26 марта 2010

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

5 голосов
/ 26 марта 2010

Это не имеет особого смысла. Указатели в 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);
    ...
}
2 голосов
/ 26 марта 2010

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

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

1 голос
/ 26 марта 2010

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

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 заботятся об этом, полностью скрывая детали реализации.

Могут быть приняты решения о дальнейших деталях, например, Уничтожение может взять node_t ** n и установить * n в NULL после его освобождения.

0 голосов
/ 31 марта 2013

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

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

HOTKEY_STATE ** plplpHotKeyStates

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

*plplpHotKeyStates = ( HOTKEY_STATE * ) calloc ( ruintNKeys ,
                                                 sizeof ( HOTKEY_STATE ) ) ;

После того, как я убедился, что plplpHotKeyStates больше не является нулевым, я определил локальную переменную-указатель, hkHotKeyStates, следующим образом.

HOTKEY_STATE * hkHotKeyStates = *plplpHotKeyStates ;

Используя эту переменную с целым числом без знака для нижнего индекса, код заполняет структуры, используя простой оператор-член (.), Как показано ниже.

hkHotKeyStates [ uintCurrKey ].ScanCode = SCANCODE_KEY_ALT ;

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

0 голосов
/ 26 марта 2010

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

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

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

0 голосов
/ 26 марта 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...