Ẩn thông tin
Lợi thế của việc trả về một con trỏ cho một cấu trúc trái ngược với trả về toàn bộ cấu trúc trong câu lệnh return của hàm là gì?
Một trong những phổ biến nhất là ẩn thông tin . C không có khả năng tạo các trường struct
riêng tư, chứ chưa nói đến việc cung cấp các phương thức để truy cập chúng.
Vì vậy, nếu bạn muốn ngăn chặn mạnh mẽ các nhà phát triển có thể nhìn thấy và can thiệp vào nội dung của một điểm, như vậy FILE
, cách duy nhất và duy nhất là ngăn họ tiếp xúc với định nghĩa của nó bằng cách coi con trỏ là mờ đục có kích thước điểm và định nghĩa là không biết với thế giới bên ngoài. Định nghĩa về FILE
ý chí sau đó chỉ hiển thị cho những người thực hiện các hoạt động yêu cầu định nghĩa của nó, như fopen
, trong khi chỉ có khai báo cấu trúc sẽ hiển thị cho tiêu đề công khai.
Tương thích nhị phân
Ẩn định nghĩa cấu trúc cũng có thể giúp cung cấp phòng thở để duy trì khả năng tương thích nhị phân trong API dylib. Nó cho phép người triển khai thư viện thay đổi các trường trong cấu trúc mờ mà không phá vỡ tính tương thích nhị phân với những người sử dụng thư viện, vì bản chất của mã của họ chỉ cần biết họ có thể làm gì với cấu trúc, chứ không phải nó lớn như thế nào hoặc trường nào nó có.
Ví dụ, tôi thực sự có thể chạy một số chương trình cổ xưa được xây dựng trong thời đại Windows 95 ngày nay (không phải lúc nào cũng hoàn hảo, nhưng đáng ngạc nhiên là nhiều chương trình vẫn hoạt động). Rất có thể là một số mã cho các nhị phân cổ đó đã sử dụng các con trỏ mờ cho các cấu trúc có kích thước và nội dung đã thay đổi từ thời Windows 95. Tuy nhiên, các chương trình tiếp tục hoạt động trong các phiên bản mới của windows vì chúng không tiếp xúc với nội dung của các cấu trúc đó. Khi làm việc trên thư viện nơi khả năng tương thích nhị phân là quan trọng, những gì khách hàng không tiếp xúc thường được phép thay đổi mà không phá vỡ tính tương thích ngược.
Hiệu quả
Trả lại một cấu trúc đầy đủ là NULL sẽ khó hơn tôi cho rằng hoặc kém hiệu quả hơn. Đây có phải là một lý do hợp lệ?
Nó thường kém hiệu quả hơn khi giả sử loại có thể phù hợp trên thực tế và được phân bổ trên ngăn xếp trừ khi thường có bộ cấp phát bộ nhớ ít khái quát hơn được sử dụng phía sau hậu trường malloc
, giống như bộ nhớ gộp của bộ cấp phát có kích thước thay đổi được phân bổ. Đây là một sự đánh đổi an toàn trong trường hợp này, rất có thể, cho phép các nhà phát triển thư viện duy trì các bất biến (đảm bảo khái niệm) liên quan đến FILE
.
Đó không phải là một lý do hợp lệ ít nhất là từ quan điểm hiệu suất để fopen
trả về một con trỏ vì lý do duy nhất nó trả về NULL
là do không mở được tệp. Điều đó sẽ tối ưu hóa một kịch bản đặc biệt để đổi lấy việc làm chậm tất cả các đường dẫn thực hiện trường hợp thông thường. Có thể có một lý do năng suất hợp lệ trong một số trường hợp để làm cho các thiết kế đơn giản hơn để làm cho chúng trả về các con trỏ để cho phép NULL
được trả về trong một số điều kiện hậu.
Đối với các hoạt động tập tin, chi phí hoạt động tương đối tầm thường so với chính các hoạt động tập tin và fclose
không thể tránh khỏi hướng dẫn sử dụng . Vì vậy, không giống như chúng ta có thể tiết kiệm cho khách hàng những rắc rối trong việc giải phóng (đóng) tài nguyên bằng cách đưa ra định nghĩa FILE
và trả lại theo giá trị fopen
hoặc hy vọng phần lớn hiệu suất được cung cấp do chi phí tương đối của các hoạt động tệp để tránh phân bổ heap .
Điểm nóng và sửa lỗi
Tuy nhiên, đối với các trường hợp khác, tôi đã lập hồ sơ rất nhiều mã C lãng phí trong các cơ sở mã di sản với các điểm nóng trong malloc
bộ nhớ cache bắt buộc và không cần thiết do sử dụng thực hành này quá thường xuyên với các con trỏ mờ và phân bổ quá nhiều thứ không cần thiết trong đống, đôi khi trong các vòng lớn.
Một cách thực hành khác mà tôi sử dụng thay vào đó là để lộ các định nghĩa cấu trúc, ngay cả khi máy khách không có ý định giả mạo chúng, bằng cách sử dụng một tiêu chuẩn quy ước đặt tên để truyền đạt rằng không ai khác nên chạm vào các trường:
struct Foo
{
/* priv_* indicates that you shouldn't tamper with these fields! */
int priv_internal_field;
int priv_other_one;
};
struct Foo foo_create(void);
void foo_destroy(struct Foo* foo);
void foo_something(struct Foo* foo);
Nếu có những lo ngại về khả năng tương thích nhị phân trong tương lai, thì tôi đã thấy nó đủ tốt để chỉ dự trữ một số không gian thừa cho các mục đích trong tương lai, như vậy:
struct Foo
{
/* priv_* indicates that you shouldn't tamper with these fields! */
int priv_internal_field;
int priv_other_one;
/* reserved for possible future uses (emergency backup plan).
currently just set to null. */
void* priv_reserved;
};
Không gian dành riêng đó hơi lãng phí nhưng có thể là một trình tiết kiệm cuộc sống nếu chúng ta thấy trong tương lai chúng ta cần thêm một số dữ liệu vào Foo
mà không phá vỡ các nhị phân sử dụng thư viện của chúng tôi.
Theo tôi, ẩn thông tin và khả năng tương thích nhị phân thường là lý do hợp lý duy nhất để chỉ cho phép phân bổ các cấu trúc heap bên cạnh các cấu trúc có độ dài thay đổi (sẽ luôn yêu cầu nó, hoặc ít nhất là hơi khó sử dụng nếu khách hàng phải phân bổ bộ nhớ trên ngăn xếp theo kiểu VLA để phân bổ VLS). Ngay cả các cấu trúc lớn thường rẻ hơn để trả về theo giá trị nếu điều đó có nghĩa là phần mềm hoạt động nhiều hơn với bộ nhớ nóng trên ngăn xếp. Và ngay cả khi chúng không rẻ hơn để trả về theo giá trị khi tạo, người ta có thể chỉ cần làm điều này:
int foo_create(struct Foo* foo);
...
/* In the client code: */
struct Foo foo;
if (foo_create(&foo))
{
foo_something(&foo);
foo_destroy(&foo);
}
... để khởi tạo Foo
từ ngăn xếp mà không có khả năng sao chép thừa. Hoặc khách hàng thậm chí có quyền tự do phân bổ Foo
trên heap nếu họ muốn vì một lý do nào đó.