Perangkap desain API di C [ditutup]

10

Apa beberapa kelemahan yang membuat Anda gila di API C (termasuk perpustakaan standar, perpustakaan pihak ketiga, dan header di dalam proyek)? Tujuannya adalah untuk mengidentifikasi perangkap desain API di C, sehingga orang yang menulis perpustakaan C baru dapat belajar dari kesalahan di masa lalu.

Jelaskan mengapa cacatnya buruk (sebaiknya dengan contoh), dan cobalah untuk menyarankan perbaikan. Meskipun solusi Anda mungkin tidak praktis dalam kehidupan nyata (ini sudah terlambat untuk diperbaiki strncpy), solusi ini harus memberi kesempatan bagi penulis perpustakaan di masa depan.

Meskipun fokus dari pertanyaan ini adalah API C, masalah yang memengaruhi kemampuan Anda untuk menggunakannya dalam bahasa lain dipersilahkan.

Tolong beri satu cacat per jawaban, sehingga demokrasi dapat mengurutkan jawaban.

Joey Adams
sumber
3
Joey, pertanyaan ini hampir tidak konstruktif dengan meminta untuk membuat daftar hal-hal yang dibenci orang. Ada potensi di sini untuk pertanyaan menjadi berguna jika jawaban menjelaskan mengapa praktik yang mereka tunjukkan itu buruk dan memberikan informasi terperinci tentang cara meningkatkannya. Untuk itu, harap pindahkan contoh Anda dari pertanyaan ke dalam jawabannya sendiri dan jelaskan mengapa itu masalah / bagaimana mallocstring akan memperbaikinya. Saya pikir memberikan contoh yang baik dengan jawaban pertama bisa sangat membantu pertanyaan ini berkembang. Terima kasih!
Adam Lear
1
@Anna Lear: Terima kasih telah memberi tahu saya mengapa pertanyaan saya bermasalah. Saya berusaha membuatnya tetap konstruktif dengan meminta contoh dan menyarankan alternatif. Saya kira saya benar-benar membutuhkan beberapa contoh untuk menunjukkan apa yang ada dalam pikiran saya.
Joey Adams
@ Joey Adams Lihat seperti ini. Anda mengajukan pertanyaan yang seharusnya "secara otomatis" menyelesaikan masalah C API secara umum. Di mana situs-situs seperti StackOverflow dirancang untuk berfungsi sedemikian rupa sehingga masalah yang lebih umum dengan pemrograman mudah ditemukan DAN dijawab. StackOverflow secara alami akan menghasilkan daftar jawaban untuk pertanyaan Anda tetapi dengan cara yang lebih mudah dicari terstruktur.
Andrew T Finnell
Saya memilih untuk menutup pertanyaan saya sendiri. Tujuan saya adalah memiliki koleksi jawaban yang dapat berfungsi sebagai daftar periksa terhadap perpustakaan C baru. Tiga jawaban sejauh ini semuanya menggunakan kata-kata seperti "tidak konsisten", "tidak logis", atau "membingungkan". Seseorang tidak dapat menentukan secara objektif apakah suatu API melanggar salah satu dari jawaban ini.
Joey Adams

Jawaban:

5

Fungsi dengan nilai pengembalian yang tidak konsisten atau tidak logis. Dua contoh bagus:

1) Beberapa fungsi windows yang mengembalikan HANDLE menggunakan NULL / 0 untuk kesalahan (CreateThread), beberapa menggunakan INVALID_HANDLE_VALUE / -1 untuk kesalahan (CreateFile).

2) Fungsi 'waktu' POSIX mengembalikan '(time_t) -1' pada kesalahan, yang benar-benar tidak masuk akal karena 'time_t' dapat berupa tipe yang ditandatangani atau tidak.

David Schwartz
sumber
2
Sebenarnya, time_t ditandatangani (biasanya). Namun, menyebut 31 Desember 1969 "tidak valid" agak tidak masuk akal. Saya kira 60-an itu kasar :-) Dalam keseriusan, solusi akan mengembalikan kode kesalahan, dan meneruskan hasilnya melalui pointer, seperti pada: int time(time_t *out);dan BOOL CreateFile(LPCTSTR lpFileName, ..., HANDLE *out);.
Joey Adams
Persis. Sungguh aneh jika time_t tidak ditandatangani, dan jika time_t ditandatangani, itu membuat satu waktu tidak valid di tengah lautan yang valid.
David Schwartz
4

Fungsi atau parameter dengan nama yang tidak deskriptif atau membingungkan. Sebagai contoh:

1) CreateFile, di Windows API, tidak benar-benar membuat file, itu membuat file handle. Itu bisa membuat file, sama seperti 'buka' bisa, jika diminta melalui parameter. Parameter ini memiliki nilai yang disebut 'CREATE_ALWAYS' dan 'CREATE_NEW' yang namanya bahkan tidak mengisyaratkan semantik mereka. (Apakah 'CREATE_ALWAYS' berarti gagal jika file tersebut ada? Atau apakah itu membuat file baru di atasnya? Apakah 'CREATE_NEW' berarti selalu membuat file baru dan gagal jika file sudah ada? Atau apakah itu membuat yang baru? file di atasnya?)

2) pthread_cond_wait di API POST pthreads POSIX, yang meskipun namanya, adalah menunggu tanpa syarat .

David Schwartz
sumber
1
The cond di pthread_cond_waittidak berarti "kondisional menunggu". Ini merujuk pada fakta bahwa Anda sedang menunggu variabel kondisi .
Jonathon Reinhart
Benar, ini adalah menunggu tanpa syarat untuk suatu kondisi.
David Schwartz
3

Jenis buram yang dilewatkan melalui antarmuka sebagai jenis yang dihapus pegangan. Masalahnya adalah, tentu saja, bahwa kompiler tidak dapat memeriksa kode pengguna untuk jenis argumen yang benar.

Ini datang dalam berbagai bentuk dan rasa, termasuk, tetapi tidak terbatas pada:

  • void* penyalahgunaan

  • menggunakan intsebagai pegangan sumber daya (contoh: perpustakaan CDI)

  • argumen yang diketik secara ketat

Jenis yang lebih berbeda (= tidak dapat digunakan sepenuhnya secara bergantian) dipetakan ke jenis yang sama dengan jenis yang dihapus, semakin buruk. Tentu saja, obatnya hanya untuk menyediakan pointer opaque yang aman di sepanjang baris (contoh 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);
cmaster - mengembalikan monica
sumber
2

Fungsi dengan konvensi pengembalian string yang tidak konsisten dan seringkali rumit.

Misalnya, getcwd meminta buffer yang disediakan pengguna dan ukurannya. Ini berarti aplikasi harus menetapkan batas arbitrer pada panjang direktori saat ini, atau melakukan sesuatu seperti ini ( dari 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);
}

Solusi saya: kembalikan mallocstring ed. Sederhana, kuat, dan tidak kalah efisien. Kecuali platform tertanam dan sistem lama, mallocsebenarnya cukup cepat.

Joey Adams
sumber
4
Saya tidak akan menyebut praktik buruk ini, saya akan menyebut praktik baik ini. 1) Sangat umum bahwa tidak ada programmer yang terkejut karenanya. 2) Meninggalkan alokasi untuk pemanggil, yang mengecualikan banyak kemungkinan bug kebocoran memori. 3) Ini kompatibel dengan buffer yang dialokasikan secara statis. 4) Ini membuat implementasi fungsi lebih bersih, sebuah fungsi yang menghitung beberapa rumus matematika tidak boleh berkaitan dengan sesuatu yang sama sekali tidak berhubungan seperti alokasi memori dinamis. Anda pikir main menjadi lebih bersih tetapi fungsinya menjadi berantakan. 5) malloc bahkan tidak diperbolehkan di banyak sistem.
@Lundin: Masalahnya adalah, hal itu menyebabkan pemrogram membuat batas kode keras yang tidak perlu, dan mereka harus berusaha sangat keras untuk tidak melakukannya (lihat contoh di atas). Ini baik-baik saja untuk hal-hal seperti snprintf(buf, 32, "%d", n), di mana panjang output diprediksi (tentu saja tidak lebih dari 30, kecuali intadalah benar-benar besar pada sistem Anda). Memang, malloc tidak tersedia di banyak sistem, tetapi untuk lingkungan desktop dan server, itu, dan berfungsi dengan sangat baik.
Joey Adams
Tetapi masalahnya adalah bahwa fungsi dalam contoh Anda tidak menetapkan batas kode keras. Kode seperti ini bukan praktik umum. Di sini, main mengetahui hal-hal tentang panjang buffer yang seharusnya diketahui fungsinya. Itu semua menunjukkan desain program yang buruk. Main sepertinya tidak tahu apa fungsi getcwd bahkan, jadi ia menggunakan alokasi "brute force" untuk mencari tahu. Di suatu tempat antarmuka antara modul di mana getcwd berada dan pemanggil kacau. Itu tidak berarti bahwa cara memanggil fungsi ini buruk, sebaliknya pengalaman menunjukkan itu baik karena alasan yang sudah saya sebutkan.
1

Fungsi yang mengambil / mengembalikan tipe data majemuk berdasarkan nilai, atau yang menggunakan panggilan balik.

Lebih buruk lagi jika jenis kata adalah gabungan atau berisi bidang bit.

Dari perspektif penelepon C, ini sebenarnya OK, tapi saya tidak menulis dalam C atau C ++ kecuali diminta, jadi saya biasanya menelepon melalui FFI. Sebagian besar FFI tidak mendukung serikat atau bit-bidang, dan beberapa (seperti Haskell dan MLton) tidak dapat mendukung struct yang diteruskan oleh nilai. Bagi mereka yang dapat menangani by-value structs, setidaknya Common Lisp dan LuaJIT dipaksa ke jalur lambat - Antarmuka Fungsi Luar Negeri Umum Lisp harus membuat panggilan lambat melalui libffi, dan LuaJIT menolak untuk JIT-mengkompilasi jalur kode yang berisi panggilan. Fungsi yang dapat memanggil kembali ke host juga memicu jalur lambat di LuaJIT, Java, dan Haskell, dengan LuaJIT tidak dapat mengkompilasi panggilan seperti itu.

Demi
sumber