2010-03-26 2 views
7

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

Каковы преимущества и/или недостатки обоих подходов?

Спасибо!

EDIT Благодарим всех вас за ответы. Мотивы выбора 1 мне очень понятны (и я должен указать, что аргумент указателя для выбора 1 должен быть malloc'd вопреки тому, что я изначально думал).

ответ

15

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

+0

Это. Возможность делать 'struct thing t; init_thing (&t); 'is nice. – dmckee

+0

Если бы я хотел написать функцию для копирования связанного списка, мог бы я иметь прототип функции, например' void copy_list (node_t * new_head, node_t * original_head) '? Я думаю, мне нужно было бы malloc a новый узел для каждого в исходном списке и добавьте его в «конец» new_head (скопируйте val и тип, но приведите ему новое значение указателя 'node_t * next'). Это лучший способ сделать это? – Tyler

+1

@ Tyler, рассмотрите 'copy_list (node_t ** new_head, const node_t * original_head)' как возможно предпочтительную подпись для этой функции (хотя этот особый случай _returning_ a 'node_t *' также является разумной возможностью). –

0

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

5

Это не имеет большого смысла. Указатели в 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 
    ... 
} 

третий является просто mallocn вне вызова функции:

int main() { 
    ... 
    node_t* n = malloc(sizeof(node_t) * SIZE); 
    create_node(n, &someint, INT); 
    ... 
} 
+0

Благодарим вас за исправление этой серьезной ошибки в моем понимании. :) – Tyler

+0

Должен добавить, что я согласен с Алексом Мартелли: третий метод, безусловно, лучший для большинства случаев. –

2

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

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

+0

«Легче забыть освободить() ваши указатели, если вы не указали malloc()». Не уверен, что я согласен с этим, но я проработал 7 лет в системе, которая не освобождала память при выходе из процесса. Это научит вас писать код без утечек. –

+0

Я не имею в виду, как ... «всегда», но, возможно, новичок не считает, что указатель, возвращаемый функцией, был внутри него. Это скорее вкус:] – mgv

+0

@Steve Какова была система и в чем ее преимущество? – Tyler

0

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

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

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

1

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

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); 
} 

Для каждого таНоса должен быть свободным, таким образом, для каждого инициализации должна быть термином и для каждого создания должно быть уничтожить. По мере того, как ваши объекты становятся более сложными, вы обнаружите, что вы начинаете их гнездовать. Некоторые объекты более высокого уровня могут использовать список node_t для внутреннего управления данными. Прежде чем освободить этот объект, сначала должен быть освобожден список. _init и _term заботятся об этом, полностью скрывая эту деталь реализации.

Возможны решения относительно дальнейшей информации, например. destroy может взять node_t ** n и установить * n в NULL после освобождения.

0

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

В случае, когда я привел меня на эту страницу, у меня есть функция, которая передает указатель, который получает адрес массива структур 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, и вызывающий абонент имеет все необходимое для обработки массива, либо обычным способом, с использованием опорного оператора (->), либо за счет использования того же метод, который я использовал в функции для получения прямого доступа к массиву.

Смежные вопросы