2010-03-26 34 views
7

Tôi nhận thấy rằng nó là một thành ngữ phổ biến trong C để chấp nhận một con trỏ ed un- malloc làm đối số thứ hai thay vì trả về một con trỏ. Ví dụ:con trỏ làm đối số thứ hai thay vì trả về con trỏ?

/*function prototype*/  
void create_node(node_t* new_node, void* _val, int _type); 

/* implementation */ 
node_t* n; 
create_node(n, &someint, INT) 

Thay vì

/* function prototype */ 
node_t* create_node(void* _val, int _type) 

/* implementation */ 
node_t* n = create_node(&someint, INT) 

những lợi thế và/hoặc nhược điểm của cả hai phương pháp là gì?

Cảm ơn!

EDIT Cảm ơn tất cả các bạn đã trả lời. Động cơ cho sự lựa chọn 1 rất rõ ràng với tôi bây giờ (và tôi nên chỉ ra rằng đối số con trỏ cho sự lựa chọn 1 nên được malloc'd trái với những gì tôi nghĩ ban đầu).

Trả lời

15

Chấp nhận một con trỏ (mà người gọi là chịu trách nhiệm về malloc'ing hay không) vào bộ nhớ được điền vào, có những ưu điểm nghiêm trọng trong sự linh hoạt hơn trở một con trỏ (nhất thiết malloc'ed). Đặc biệt, nếu người gọi biết nó cần sử dụng bất cứ thứ gì được trả về chỉ trong một hàm nhất định, nó có thể chuyển vào địa chỉ của một cấu trúc hoặc mảng được phân bổ theo từng ngăn xếp; nếu nó biết nó không cần reentrancy, nó có thể vượt qua trong địa chỉ của một cấu trúc hoặc mảng static - trong cả hai trường hợp, một cặp malloc/miễn phí được lưu lại, và tiết kiệm như vậy làm gắn lên! -)

+0

Điều này. Khả năng thực hiện 'struct điều t; init_thing (&t); 'là tốt đẹp. – dmckee

+0

Nếu tôi muốn viết một chức năng để sao chép một danh sách liên kết, tôi sẽ có một nguyên mẫu chức năng như' void copy_list (node_t * new_head, node_t * original_head) '? Tôi nghĩ rằng tôi sẽ phải malloc một nút mới cho mỗi trong danh sách ban đầu và thêm nó vào 'kết thúc' của new_head (sao chép val và type nhưng cung cấp cho nó một giá trị con trỏ 'node_t * next' mới) .Đây có phải là cách tốt nhất để thực hiện nó? – Tyler

+1

@ Tyler, hãy xem xét 'copy_list (node_t ** new_head, const node_t * original_head)' như một chữ ký có thể thích hợp hơn cho hàm đó (mặc dù trường hợp đặc biệt này _returning_ một 'node_t *' chắc chắn cũng là một khả năng hợp lý) –

0

cá nhân Tôi muốn trả lại dữ liệu bằng tham số giới thiệu hoặc tham số con trỏ và sử dụng hàm trả về cho mã lỗi trả lại.

5

không có ý nghĩa nhiều. Con trỏ trong C được truyền theo giá trị, giống như các đối tượng khác — sự khác biệt nằm trong giá trị. Với con trỏ, giá trị là địa chỉ bộ nhớ, được chuyển đến hàm. Tuy nhiên, bạn vẫn sao chép giá trị, và vì vậy khi bạn malloc, bạn sẽ thay đổi giá trị của con trỏ bên trong hàm của bạn, chứ không phải là giá trị ở bên ngoài.

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 
    ... 
} 

Có ba cách để tránh điều này. Đầu tiên, như bạn đã đề cập, trả về con trỏ mới từ hàm. Thứ hai là để có một con trỏ đến con trỏ, từ đó đi qua nó bằng cách tham khảo:

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 
    ... 
} 

Thứ ba là chỉ đơn giản là mallocn ngoài gọi hàm:

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

Cảm ơn bạn đã sửa lỗi chính trong sự hiểu biết của tôi. :) – Tyler

+0

Tôi có lẽ nên thêm rằng tôi đồng ý với Alex Martelli: phương pháp thứ ba chắc chắn là tốt nhất cho phần lớn các trường hợp. –

2

Tôi thường thích nhận con trỏ (bất động sản được khởi tạo) như các đối số hàm như trái ngược với trả về một con trỏ tới vùng bộ nhớ đã được malloc'ed bên trong hàm. Với cách tiếp cận này, bạn đang làm rõ rằng trách nhiệm của quản lý bộ nhớ là ở phía người dùng.

Con trỏ trở lại thường dẫn đến rò rỉ bộ nhớ, vì dễ dàng hơn để quên miễn phí() con trỏ của bạn nếu bạn không sửa đổi().

+0

"Nó dễ dàng hơn để quên miễn phí() con trỏ của bạn nếu bạn không malloc() 'ed chúng." Tôi không chắc chắn rằng tôi đồng ý với điều đó, nhưng tôi đã làm việc trong 7 năm trên một hệ thống không giải phóng bộ nhớ khi thoát khỏi quá trình. Điều đó dạy bạn cách viết mã không bị rò rỉ. –

+0

Tôi không có nghĩa là ... "luôn luôn", nhưng có lẽ một người mới không tưởng tượng rằng con trỏ mà hàm trả về đã được malloced bên trong nó. Đó là một hương vị-điều:] – mgv

+0

@Steve Hệ thống là gì và lợi thế của điều đó là gì? – Tyler

0

1) Như Samir chỉ ra mã là không chính xác, con trỏ được truyền theo giá trị, bạn cần **

2) Các chức năng cơ bản là một nhà xây dựng, vì vậy nó có ý nghĩa cho nó để cả hai cấp phát bộ nhớ và init cấu trúc dữ liệu. Mã C sạch là hầu như luôn luôn hướng đối tượng như thế với các nhà xây dựng và destructors.

3) Chức năng của bạn bị vô hiệu, nhưng nó phải trả về int để nó có thể trả về lỗi. Sẽ có ít nhất 2, có thể 3 điều kiện lỗi có thể xảy ra: malloc có thể thất bại, kiểu đối số có thể không hợp lệ và có thể giá trị có thể nằm ngoài phạm vi.

1

Tôi thường không đưa ra lựa chọn cố định, tôi đặt nó vào thư viện riêng của mình và cung cấp tốt nhất cho cả hai thế giới.

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

Đối với mỗi malloc nên có miễn phí, do đó, đối với mỗi init phải có cụm từ và mọi lần tạo nên có một phá hủy. Khi đối tượng của bạn phát triển phức tạp hơn, bạn sẽ thấy rằng bạn bắt đầu làm tổ chúng. Một số đối tượng cấp cao hơn có thể sử dụng một danh sách node_t để quản lý dữ liệu nội bộ. Trước khi giải phóng đối tượng này, danh sách phải được giải phóng trước tiên. _init và _term chăm sóc cho điều này, hoàn toàn ẩn chi tiết thực hiện này.

Có thể có quyết định về các chi tiết khác, ví dụ: phá hủy có thể mất một node_t ** n và đặt * n thành NULL sau khi giải phóng nó.

0

Một vấn đề không được thảo luận trong bài viết này là vấn đề về cách bạn tham khảo bộ đệm malloc'ed trong hàm phân bổ nó và có lẽ lưu trữ một số thứ trong đó trước khi nó trả về điều khiển cho người gọi.

Trong trường hợp đã đưa tôi đến trang này, tôi có một hàm chuyển một con trỏ, nó nhận địa chỉ của một mảng các cấu trúc HOTKEY_STATE. Nguyên mẫu khai báo đối số như sau.

HOTKEY_STATE ** plplpHotKeyStates 

Giá trị trả về, ruintNút, là số phần tử được xác định bởi thường trình trước khi bộ đệm được cấp phát. Thay vì sử dụng malloc() trực tiếp, tuy nhiên, tôi đã sử dụng calloc, như sau.

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

Sau khi tôi xác minh rằng plplpHotKeyStates không còn rỗng, tôi đã xác định biến con trỏ cục bộ, hkHotKeyStates, như sau.

HOTKEY_STATE * hkHotKeyStates = *plplpHotKeyStates ; 

Sử dụng biến này, với số nguyên chưa dấu cho subscript, mã sẽ điền cấu trúc, sử dụng toán tử thành viên đơn giản (.), Như được hiển thị bên dưới.

hkHotKeyStates [ uintCurrKey ].ScanCode = SCANCODE_KEY_ALT ; 

Khi mảng được phổ biến đầy đủ, nó sẽ trả ruintNKeys, và người gọi có mọi thứ nó cần phải xử lý mảng, hoặc theo cách thông thường, sử dụng toán tử tham chiếu (->), hoặc bằng cách sử dụng cùng kỹ thuật mà tôi đã sử dụng trong hàm để truy cập trực tiếp vào mảng.

Các vấn đề liên quan