Cạm bẫy thiết kế API trong C [đã đóng]


10

Một số lỗ hổng khiến bạn thất vọng trong API C (bao gồm thư viện tiêu chuẩn, thư viện bên thứ ba và tiêu đề bên trong dự án) là gì? Mục tiêu là xác định các cạm bẫy thiết kế API trong C, vì vậy những người viết thư viện C mới có thể học hỏi từ những sai lầm trong quá khứ.

Giải thích tại sao lỗ hổng là xấu (tốt nhất là với một ví dụ) và cố gắng đề xuất một cải tiến. Mặc dù giải pháp của bạn có thể không thực tế trong cuộc sống thực (đã quá muộn để khắc phục strncpy), nhưng nó sẽ hỗ trợ cho các nhà văn thư viện trong tương lai.

Mặc dù trọng tâm của câu hỏi này là API C, các vấn đề ảnh hưởng đến khả năng sử dụng chúng trong các ngôn ngữ khác của bạn đều được chào đón.

Xin vui lòng cho một lỗ hổng cho mỗi câu trả lời, để dân chủ có thể sắp xếp các câu trả lời.


3
Joey, câu hỏi này được nhấn mạnh là không mang tính xây dựng bằng cách yêu cầu xây dựng một danh sách những điều mọi người ghét. Có tiềm năng ở đây cho câu hỏi hữu ích nếu câu trả lời giải thích tại sao các thực tiễn họ chỉ ra là xấu và cung cấp thông tin chi tiết về cách cải thiện chúng. Cuối cùng, vui lòng chuyển ví dụ của bạn từ câu hỏi sang câu trả lời của riêng mình và giải thích lý do tại sao đó là một vấn đề / làm thế nào một mallocchuỗi 'sẽ sửa nó. Tôi nghĩ rằng việc nêu gương tốt với câu trả lời đầu tiên thực sự có thể giúp câu hỏi này phát triển mạnh. Cảm ơn!
Adam Lear

1
@Anna Lear: Cảm ơn đã cho tôi biết lý do tại sao câu hỏi của tôi có vấn đề. Tôi đã cố gắng giữ cho nó mang tính xây dựng bằng cách yêu cầu một ví dụ và đề xuất thay thế. Tôi đoán tôi thực sự cần một số ví dụ để chỉ ra những gì tôi đã nghĩ trong đầu.
Joey Adams

@Joey Adams Hãy nhìn nó theo cách này. Bạn đang hỏi một câu hỏi được cho là "tự động" giải quyết các vấn đề API C một cách chung chung. Nơi các trang web như StackOverflow được thiết kế để hoạt động sao cho các vấn đề phổ biến hơn với lập trình dễ dàng được tìm thấy VÀ được trả lời. StackOverflow đương nhiên sẽ dẫn đến một danh sách các câu trả lời cho câu hỏi của bạn nhưng theo cách dễ tìm kiếm hơn có cấu trúc.
Andrew T Finnell

Tôi đã bỏ phiếu để đóng câu hỏi của riêng tôi. Mục tiêu của tôi là có một bộ sưu tập các câu trả lời có thể dùng làm danh sách kiểm tra đối với các thư viện C mới. Cả ba câu trả lời cho đến nay đều sử dụng các từ như "không nhất quán", "phi logic" hoặc "khó hiểu". Người ta không thể xác định một cách khách quan liệu API có vi phạm bất kỳ câu trả lời nào trong số này không.
Joey Adams

Câu trả lời:


5

Các hàm với giá trị trả về không nhất quán hoặc không hợp lý. Hai ví dụ điển hình:

1) Một số chức năng của windows trả về TAY sử dụng NULL / 0 cho một lỗi (CreatThread), một số sử dụng INVALID_HANDLE_VALUE / -1 cho một lỗi (CreatFile).

2) Lỗi 'thời gian' của hàm POSIX '(time_t) -1', điều này thực sự phi logic vì 'time_t' có thể là loại đã ký hoặc không dấu.


2
Trên thực tế, time_t là (thường) được ký. Tuy nhiên, gọi ngày 31 tháng 12 năm 1969 là "không hợp lệ" là khá phi logic. Tôi đoán thập niên 60 rất khó hiểu :-) Nói một cách nghiêm túc, một giải pháp sẽ là trả về mã lỗi và chuyển kết quả qua một con trỏ, như trong: int time(time_t *out);BOOL CreateFile(LPCTSTR lpFileName, ..., HANDLE *out);.
Joey Adams

Chính xác. Thật kỳ lạ nếu time_t không được ký và nếu time_t được ký, nó làm cho một lần không hợp lệ ở giữa một đại dương của những cái hợp lệ.
David Schwartz

4

Hàm hoặc tham số với tên không mô tả hoặc xác nhận gây nhầm lẫn. Ví dụ:

1) CreateFile, trong API Windows, không thực sự tạo một tệp, nó tạo ra một tệp xử lý. Nó có thể tạo một tệp, giống như 'mở' có thể, nếu được yêu cầu thông qua một tham số. Tham số này có các giá trị được gọi là 'CREATE_ALWAYS' và 'CREATE_NEW' có tên thậm chí không gợi ý về ngữ nghĩa của chúng. ('CREATE_ALWAYS' có nghĩa là nó không thành công nếu tập tin tồn tại? Hay nó tạo một tập tin mới ở trên nó? tập tin trên đầu trang của nó?)

2) pthread_cond_wait trong API pthreads POSIX, mặc dù tên của nó, là một sự chờ đợi vô điều kiện .


1
Các cond trong pthread_cond_waitkhông có nghĩa là "chờ đợi có điều kiện". Nó đề cập đến thực tế là bạn đang chờ đợi một biến điều kiện .
Jonathon Reinhart

Phải, đó là một sự chờ đợi vô điều kiện cho một điều kiện.
David Schwartz

3

Các loại mờ đục được truyền qua giao diện dưới dạng loại xử lý đã xóa. Tất nhiên, vấn đề là trình biên dịch không thể kiểm tra mã người dùng để biết các loại đối số chính xác.

Điều này có nhiều hình thức và hương vị khác nhau, bao gồm, nhưng không giới hạn ở:

  • void* lạm dụng

  • sử dụng intnhư một trình xử lý tài nguyên (ví dụ: thư viện CDI)

  • đối số được gõ

Các loại khác biệt hơn (= không thể được sử dụng thay thế hoàn toàn cho nhau) được ánh xạ tới cùng loại đã xóa, càng tệ. Tất nhiên, biện pháp khắc phục chỉ đơn giản là cung cấp các con trỏ mờ đục an toàn dọc theo dòng (ví dụ C):

typedef struct Foo Foo;
typedef struct Bar Bar;

Foo* createFoo();
Bar* createBar();

int doSomething(Foo* foo);
void somethingElse(Foo* foo, Bar* bar);

void destroyFoo(Foo* foo);
void destroyBar(Bar* bar);

2

Các hàm với các quy ước trả về chuỗi không nhất quán và thường rườm rà.

Ví dụ, getcwd yêu cầu bộ đệm do người dùng cung cấp và kích thước của nó. Điều này có nghĩa là một ứng dụng phải đặt giới hạn tùy ý về độ dài thư mục hiện tại hoặc thực hiện một cái gì đó như thế này ( từ CCAN ):

 /* *This* is why people hate C. */
len = 32;
cwd = talloc_array(ctx, char, len);
while (!getcwd(cwd, len)) {
    if (errno != ERANGE) {
        talloc_free(cwd);
        return NULL;
    }
    cwd = talloc_realloc(ctx, cwd, char, len *= 2);
}

Giải pháp của tôi: trả về một mallocchuỗi ed. Nó đơn giản, mạnh mẽ và không kém phần hiệu quả. Ngoại trừ các nền tảng nhúng và các hệ thống cũ hơn, mallocthực sự khá nhanh.


4
Tôi sẽ không gọi đây là thực hành xấu, tôi sẽ gọi đây là thực hành tốt. 1) Nó phổ biến đến mức không có lập trình viên nào phải ngạc nhiên về nó. 2) Nó để lại sự phân bổ cho người gọi, loại trừ nhiều khả năng lỗi rò rỉ bộ nhớ. 3) Nó tương thích với các bộ đệm được phân bổ tĩnh. 4) Nó làm cho việc thực thi hàm sạch hơn, một hàm tính toán một số công thức toán học không nên liên quan đến một cái gì đó hoàn toàn không liên quan như phân bổ bộ nhớ động. Bạn nghĩ rằng chính được sạch hơn nhưng chức năng trở nên lộn xộn hơn. 5) malloc thậm chí không được phép trên nhiều hệ thống.

@Lundin: Vấn đề là, nó dẫn đến việc các lập trình viên tạo ra các giới hạn mã hóa cứng không cần thiết và họ phải cố gắng hết sức để không (xem ví dụ ở trên). Đó là tốt cho những thứ như thế snprintf(buf, 32, "%d", n), nơi chiều dài đầu ra là có thể dự đoán (chắc chắn không phải hơn 30, trừ khi intthực sự lớn trên hệ thống của bạn). Thật vậy, malloc không có sẵn trên nhiều hệ thống, nhưng đối với môi trường máy tính để bàn và máy chủ thì có, và nó hoạt động rất tốt.
Joey Adams

Nhưng vấn đề là hàm trong ví dụ của bạn không đặt giới hạn mã hóa cứng. Mã như thế này không phải là thông lệ. Ở đây, main biết những điều về chiều dài bộ đệm mà hàm nên biết. Tất cả cho thấy thiết kế chương trình kém. Main dường như không biết chức năng getcwd thậm chí còn làm gì, vì vậy nó đang sử dụng một số phân bổ "lực lượng vũ phu" để tìm hiểu. Đâu đó giao diện giữa mô-đun trong đó getcwd cư trú và người gọi bị nhầm lẫn. Điều đó không có nghĩa là cách gọi chức năng này là xấu, ngược lại kinh nghiệm cho thấy nó tốt cho những lý do tôi đã liệt kê.

1

Các hàm lấy / trả về các kiểu dữ liệu hỗn hợp theo giá trị hoặc sử dụng các hàm gọi lại.

Thậm chí tệ hơn nếu loại nói là một liên minh hoặc chứa các trường bit.

Từ quan điểm của một người gọi C, những điều này thực sự ổn, nhưng tôi không viết bằng C hoặc C ++ trừ khi được yêu cầu, vì vậy tôi thường gọi qua FFI. Hầu hết các FFI không hỗ trợ các công đoàn hoặc trường bit và một số (như Haskell và MLton) không thể hỗ trợ các cấu trúc được truyền theo giá trị. Đối với những người có thể xử lý các cấu trúc theo giá trị, ít nhất Common Lisp và LuaJIT bị buộc vào các đường dẫn chậm - Giao diện chức năng đối ngoại chung của Lisp phải thực hiện cuộc gọi chậm qua libffi và LuaJIT từ chối biên dịch đường dẫn mã chứa cuộc gọi. Các hàm có thể gọi lại vào máy chủ cũng kích hoạt các đường dẫn chậm trên LuaJIT, Java và Haskell, với LuaJIT không thể biên dịch cuộc gọi như vậy.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.