2009-03-09 33 views
8

Tôi đang làm việc trên một thư viện hỗ trợ nhiều môi trường lập trình như VB6 và FoxPro. Tôi phải gắn bó với quy ước C vì nó là mẫu số chung thấp nhất. Bây giờ tôi có một câu hỏi liên quan đến phong cách.Kiểu của hàm API C

Giả sử rằng quá trình hàm nhập và trả về một chuỗi. Trong quá trình này, lỗi có thể xảy ra. Phong cách đề xuất hiện nay là thế này:

int func(input params... char* buffer, unsigned int* buffer_size); 

Những điều tốt về phong cách này là tất cả mọi thứ được bao gồm trong nguyên mẫu, bao gồm cả mã lỗi. Và việc cấp phát bộ nhớ có thể tránh được. Vấn đề là chức năng khá dài dòng. Và bởi vì buffer_size có thể là bất kỳ, nó đòi hỏi nhiều mã hơn để thực hiện.

Một lựa chọn khác là để trở về char *, và trở về NULL để chỉ ra lỗi:

char* func(input params...); 

phong cách này đòi hỏi người gọi để xóa bộ đệm. Yêu cầu cấp phát bộ nhớ để chương trình máy chủ có thể gặp sự cố phân mảnh bộ nhớ.

Một biến thể của tùy chọn thứ hai là sử dụng biến cục bộ thread để giữ con trỏ trả về char *, để người dùng không cần xóa bộ đệm.

Bạn thích kiểu nào? Và lý do?

+0

Không phải loại đệm là char **? Ngoài ra, tại sao bạn cần một buffer_size trong tùy chọn một và không phải trong tùy chọn hai? – mweerden

+0

Ông vượt qua một bộ đệm preallocated như tham số trong và hy vọng các chức năng được gọi là để điền vào nó với văn bản lỗi. – sharptooth

+0

Ok, nhưng sau đó buffer_size không cần phải là một con trỏ, phải không? – mweerden

Trả lời

1

Biến thể thứ hai là sạch hơn.

COM IErrorInfo là triển khai phương pháp thứ hai. Máy chủ gọi SetErrorInfo để đặt chi tiết về những gì đã xảy ra và trả về mã lỗi. Người gọi kiểm tra mã và có thể gọi GetErrorInfo để lấy chi tiết. Người gọi có trách nhiệm phát hành IErrorInfo, nhưng việc chuyển các tham số của mỗi cuộc gọi trong biến thể đầu tiên cũng không đẹp.

Máy chủ có thể cấp phát đủ bộ nhớ khi khởi động để máy có đủ bộ nhớ để trả về chi tiết lỗi.

2

Nếu tôi phải chọn giữa hai kiểu được hiển thị, tôi sẽ chuyển sang chế độ đầu tiên mỗi lần. Phong cách thứ 2 cung cấp cho người dùng thư viện của bạn một cái gì đó khác để suy nghĩ, phân bổ memeory, và ai đó bị ràng buộc để quên để giải phóng bộ nhớ.

5

Tôi muốn định nghĩa đầu tiên, nơi bộ đệm và kích thước của nó được truyền vào. Có những ngoại lệ, nhưng thường bạn không mong đợi phải dọn dẹp sau các hàm bạn gọi. Trong khi đó nếu tôi cấp phát bộ nhớ và chuyển nó vào một hàm, thì tôi biết rằng tôi phải dọn dẹp sau bản thân mình.

Xử lý bộ đệm có kích thước khác nhau không phải là vấn đề lớn.

+0

Điều này phần lớn là làm thế nào Windows API chính nó hiện nó, do đó, nó hợp lý để thi đua đó. –

2

Một vấn đề khác với kiểu thứ hai là các ngữ cảnh mà bộ nhớ được cấp phát có thể khác nhau. Ví dụ:

// your library in C 
char * foo() { 
    return malloc(100); 
} 

// my client code C++ 
char * p = foo();  // call your code 
delete p;    // natural for me to do, but ... aaargh! 

Và đây chỉ là một phần nhỏ của sự cố. Bạn có thể nói rằng cả hai bên nên sử dụng malloc & miễn phí, nhưng nếu họ đang sử dụng triển khai trình biên dịch diffeent thì sao? Nó là tốt hơn cho tất cả các phân bổ và deallocations xảy ra trong cùng một vị trí. cho dù đây là thư viện r mã khách hàng là tùy thuộc vào bạn.

1

Ấn bản đầu tiên sẽ ít bị lỗi hơn khi các lập trình viên khác sử dụng nó.

Nếu người lập trình phải tự cấp phát bộ nhớ, họ có nhiều khả năng nhớ để giải phóng bộ nhớ đó. Nếu một thư viện cấp phát bộ nhớ cho chúng thì đó là một trừu tượng khác và có thể/sẽ dẫn đến các biến chứng.

1

Rất ít điều cần suy ngẫm;

  • Phân bổ và deallocation sẽ xảy ra ở cùng một phạm vi (lý tưởng). Tốt nhất là nên vượt qua bộ đệm được phân bổ trước bởi người gọi. Người gọi có thể an toàn miễn phí sau này. Điều này đặt ra câu hỏi - bộ đệm lớn như thế nào? Một cách tiếp cận mà tôi đã nhìn thấy được sử dụng khá rộng rãi trong Win32 là để vượt qua NULL như là bộ đệm đầu vào và tham số size sẽ cho bạn biết bạn cần bao nhiêu.

  • Bạn có thể giám sát bao nhiêu điều kiện lỗi có thể xảy ra? Trả lại char* có thể giới hạn phạm vi báo cáo lỗi.

  • Bạn muốn hoàn thành điều kiện trước và sau nào? Nguyên mẫu của bạn có phản ánh điều đó không?

  • Bạn có kiểm tra lỗi trong người gọi hoặc callee không?

Tôi thực sự không thể cho bạn biết cái nào tốt hơn cái kia vì tôi không có bức tranh lớn. Nhưng tôi chắc chắn những điều này có thể giúp bạn bắt đầu suy nghĩ cũng như các bài đăng khác.

8

Tôi có một chút "hàng hóa bị hỏng" khi nói đến chủ đề này. Tôi đã từng thiết kế và duy trì các API khá lớn cho viễn thông được nhúng. Một bối cảnh mà bạn không thể lấy bất cứ điều gì để được cấp. Không, ngay cả những thứ như biến toàn cục hay TLS. Đôi khi ngay cả bộ đệm heap hiện lên mà thực sự được giải quyết bộ nhớ ROM. Do đó, nếu bạn đang tìm "mẫu số chung thấp nhất", bạn cũng có thể muốn nghĩ về cấu trúc ngôn ngữ nào có sẵn trong môi trường đích của bạn (trình biên dịch có khả năng chấp nhận bất cứ điều gì trong tiêu chuẩn C, nhưng nếu có điều gì đó không được hỗ trợ, trình liên kết sẽ nói không).

Có nói rằng, Tôi sẽ luôn thay thế 1. Một phần vì (như những người khác đã chỉ ra), bạn không bao giờ nên cấp phát bộ nhớ cho người dùng một cách trực tiếp (một cách tiếp cận gián tiếp được giải thích sâu hơn). Ngay cả khi người dùng được đảm bảo làm việc với C thuần túy và thuần túy, họ vẫn có thể sử dụng API quản lý bộ nhớ tùy chỉnh của riêng họ để theo dõi rò rỉ, ghi nhật ký chẩn đoán, vv.

Thông báo lỗi là một trong những điều quan trọng nhất khi giao dịch với API. Vì người dùng có thể có các cách khác nhau để xử lý các lỗi trong mã của mình, bạn nên nhất quán nhất có thể về giao tiếp này trong suốt API. Người dùng có thể xử lý lỗi theo hướng API của bạn theo cách nhất quán và với mã tối thiểu. Tôi thường sẽ luôn khuyên bạn nên sử dụng mã enum rõ ràng hoặc định nghĩa/typedefs. Cá nhân tôi thích typedef: ed enums:

typedef enum { 

    RESULT_ONE, 
    RESULT_TWO 

} RESULT; 

..bởi vì nó cung cấp an toàn kiểu/bài tập.

Có chức năng get-last-error cũng tốt (yêu cầu lưu trữ trung tâm), cá nhân tôi chỉ sử dụng nó để cung cấp thêm thông tin về lỗi đã được nhận dạng.

Các verbosity của phương án 1 có thể được hạn chế bằng cách làm cho các hợp chất đơn giản như thế này:

struct Buffer 
{ 
    unsigned long size; 
    char* data; 
}; 

Sau đó api của bạn có thể trông đẹp hơn:

ERROR_CODE func(params... , Buffer* outBuffer); 

Chiến lược này cũng mở ra cho phức tạp hơn cơ chế. Nói ví dụ bạn PHẢI có thể phân bổ bộ nhớ cho người sử dụng (ví dụ như nếu bạn cần phải thay đổi kích thước bộ đệm), sau đó bạn có thể cung cấp một cách tiếp cận gián tiếp như sau:

struct Buffer 
{ 
    unsigned long size; 
    char* data; 
    void* (*allocator_callback)(unsigned long size); 
    void (*free_callback)(void* p); 
}; 

Ofcourse, phong cách của các cấu trúc như vậy luôn luôn là mở cho cuộc tranh luận nghiêm túc.

Chúc may mắn!

+0

Bạn không thể chuyển cấu trúc Bộ đệm đó bằng cách sao chép trên ngăn xếp thay thế. Đó là một chút ngu ngốc để sử dụng bộ nhớ động cho rằng (tôi giả sử bạn làm) – toto

+1

@toto: Lưu ý rằng bộ đệm không _have_ là bộ nhớ động chỉ vì chức năng có một con trỏ. Nó rất tốt có thể là một ví dụ ngăn xếp thông qua địa chỉ. Tuy nhiên, tôi thường sẽ ủng hộ việc sử dụng một cấu trúc đệm như thế này như một kiểu chung, thay vì gói kích thước và dữ liệu trong một cấu trúc ngăn xếp mỗi lần bạn cần truyền nó cho một hàm. Tôi cho rằng đó là những gì bạn đang nghĩ về việc nói về các cấu trúc được xếp chồng lên nhau. – sharkin

+0

Yea xin lỗi, đó chỉ là một số thiệt hại còn sót lại của Java mà tôi có. Một con trỏ là tốt cho một đối tượng trên stack. – toto

0

tôi muốn làm điều đó tương tự như cách đầu tiên, nhưng chỉ cần tinh tế khác nhau, sau khi mô hình của snprintf và chức năng tương tự:

int func(char* buffer, size_t buffer_size, input params...); 

Bằng cách này, nếu bạn có rất nhiều trong số này, họ có thể trông giống như và bạn có thể sử dụng số lượng đối số thay đổi ở bất kỳ nơi nào hữu ích.

Tôi đồng ý rất nhiều với những lý do đã trách nhiệm tham mưu cho việc sử dụng phiên bản 1 chứ không phải là phiên bản 2 - vấn đề bộ nhớ có nhiều khả năng với phiên bản 2.

+0

Tôi không nghĩ anh ta đang đề cập đến dấu ba chấm bằng cách sử dụng '...'. Nếu không có gì khác, dấu ba chấm phải luôn nằm cuối trong danh sách đối số. – sharkin

+0

Tôi chắc chắn anh ấy không đề cập đến nó, nhưng nó đã là ý tưởng sắp xếp lại các thông số. =) –

1

gì về việc sử dụng cả hai phương pháp? Tôi đồng ý với sự đồng thuận của câu trả lời ưu kiểu 1 vs những cạm bẫy của phong cách 2. Tôi cảm thấy phong cách 2 có thể được sử dụng nếu tất cả của API của bạn theo một cách đặt tên thành ngữ phù hợp, như vậy:


// Style 1 functions 
int fooBuff(char* buffer, unsigned int buffer_size, input params...); 

// Style 2 functions 
char* fooBuffAlloc(input params...); 
bool fooBuffFree(char* foo); 

/D