Menyembunyikan Informasi
Apa keuntungan dari mengembalikan pointer ke struktur sebagai lawan mengembalikan seluruh struktur dalam pernyataan kembali fungsi?
Yang paling umum adalah penyembunyian informasi . C tidak memiliki, katakanlah, kemampuan untuk membuat bidang struct
pribadi, apalagi memberikan metode untuk mengaksesnya.
Jadi, jika Anda ingin secara paksa mencegah pengembang tidak dapat melihat dan merusak konten pointee, seperti FILE
, maka satu-satunya cara adalah mencegah mereka dari terkena definisi dengan memperlakukan pointer sebagai buram yang ukuran pointee-nya dan ukurannya. definisi tidak diketahui dunia luar. Definisi FILE
maka hanya akan terlihat oleh mereka yang melaksanakan operasi yang memerlukan definisi, seperti fopen
, sementara hanya deklarasi struktur akan terlihat oleh header publik.
Kompatibilitas Biner
Menyembunyikan definisi struktur juga dapat membantu menyediakan ruang bernapas untuk menjaga kompatibilitas biner dalam API dylib. Hal ini memungkinkan pelaksana perpustakaan untuk mengubah bidang dalam struktur buram tanpa memutus kompatibilitas biner dengan mereka yang menggunakan perpustakaan, karena sifat kode mereka hanya perlu tahu apa yang dapat mereka lakukan dengan struktur, bukan seberapa besar itu atau bidang apa memiliki.
Sebagai contoh, saya benar-benar dapat menjalankan beberapa program kuno yang dibangun selama era Windows 95 hari ini (tidak selalu sempurna, tetapi ternyata masih banyak yang berfungsi). Kemungkinannya adalah beberapa kode untuk binari kuno itu menggunakan pointer buram ke struktur yang ukuran dan isinya telah berubah dari era Windows 95. Namun program terus bekerja di versi windows baru karena mereka tidak terpapar dengan isi struktur tersebut. Ketika bekerja di perpustakaan di mana kompatibilitas biner penting, apa yang klien tidak terpapar pada umumnya diizinkan untuk berubah tanpa merusak kompatibilitas ke belakang.
Efisiensi
Mengembalikan struktur penuh yang NULL akan lebih sulit saya kira atau kurang efisien. Apakah ini alasan yang sah?
Ini biasanya kurang efisien dengan asumsi tipe tersebut praktis dapat ditampung dan dialokasikan pada stack kecuali jika biasanya ada pengalokasi memori yang jauh lebih umum digunakan di belakang layar daripada malloc
, seperti memori pooling pengalokasi berukuran berukuran lebih daripada variabel yang sudah dialokasikan. Ini merupakan trade-off keselamatan dalam kasus ini, kemungkinan besar, untuk memungkinkan pengembang perpustakaan untuk mempertahankan invarian (jaminan konseptual) terkait FILE
.
Itu bukan alasan yang valid setidaknya dari sudut pandang kinerja untuk membuat fopen
pengembalian pointer karena satu-satunya alasan itu akan kembali NULL
adalah kegagalan untuk membuka file. Itu akan mengoptimalkan skenario luar biasa dengan imbalan memperlambat semua jalur eksekusi kasus umum. Mungkin ada alasan produktivitas yang valid dalam beberapa kasus untuk membuat desain lebih mudah untuk membuatnya kembali pointer untuk memungkinkan NULL
dikembalikan pada beberapa kondisi pasca.
Untuk operasi file, overhead relatif cukup sepele dibandingkan dengan operasi file itu sendiri, dan manual perlu fclose
tidak dapat dihindari. Jadi tidak seperti kita dapat menyelamatkan klien dari kesulitan membebaskan (menutup) sumber daya dengan mengekspos definisi FILE
dan mengembalikannya dengan nilai fopen
atau mengharapkan banyak peningkatan kinerja mengingat biaya relatif dari operasi file sendiri untuk menghindari alokasi tumpukan .
Hotspot dan Perbaikan
Namun untuk kasus lain, saya telah membuat banyak kode C yang sia-sia dalam basis kode lama dengan hotspot malloc
dan cache wajib yang tidak perlu karena penggunaan praktik ini terlalu sering dengan pointer buram dan mengalokasikan terlalu banyak hal secara sia-sia di tumpukan, kadang-kadang di loop besar.
Praktik alternatif yang saya gunakan sebagai gantinya adalah mengekspos definisi struktur, bahkan jika klien tidak bermaksud merusaknya, dengan menggunakan standar konvensi penamaan untuk berkomunikasi bahwa tidak ada orang lain yang boleh menyentuh bidang:
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);
Jika ada masalah kompatibilitas biner di masa depan, maka saya merasa cukup baik untuk hanya menyediakan ruang ekstra untuk keperluan di masa mendatang, seperti:
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;
};
Ruang yang dipesan itu agak boros tetapi bisa menjadi penyelamat jika kita menemukan di masa depan bahwa kita perlu menambahkan lebih banyak data Foo
tanpa merusak biner yang menggunakan perpustakaan kita.
Menurut pendapat saya, penyembunyian informasi dan kompatibilitas biner biasanya merupakan satu-satunya alasan yang layak untuk hanya mengijinkan alokasi tumpukan struktur di samping struct variabel-panjang (yang akan selalu membutuhkannya, atau setidaknya menjadi sedikit canggung untuk digunakan sebaliknya jika klien harus mengalokasikan memori pada tumpukan dengan cara VLA untuk mengalokasikan VLS). Bahkan struct besar seringkali lebih murah untuk dikembalikan dengan nilai jika itu berarti perangkat lunak bekerja lebih banyak dengan memori panas pada stack. Dan bahkan jika mereka tidak lebih murah untuk kembali berdasarkan nilai pada kreasi, orang bisa melakukan ini:
int foo_create(struct Foo* foo);
...
/* In the client code: */
struct Foo foo;
if (foo_create(&foo))
{
foo_something(&foo);
foo_destroy(&foo);
}
... untuk memulai Foo
dari tumpukan tanpa kemungkinan salinan yang berlebihan. Atau klien bahkan memiliki kebebasan untuk mengalokasikan Foo
pada heap jika mereka ingin karena suatu alasan.