C dilinde API tasarım tuzakları [kapalı]


10

C API'larında sizi çıldırtan bazı kusurlar nelerdir (standart kütüphaneler, üçüncü taraf kütüphaneleri ve bir projenin içindeki başlıklar dahil)? Amaç, C'deki API tasarım tuzaklarını tanımlamaktır, böylece yeni C kütüphaneleri yazan insanlar geçmişin hatalarından öğrenebilirler.

Kusurun neden kötü olduğunu (tercihen bir örnekle) açıklayın ve bir iyileştirme önermeye çalışın. Çözümünüz gerçek hayatta pratik olmasa da (düzeltmek için çok geç strncpy), gelecekteki kütüphane yazarları için kafa yormalıdır.

Bu sorunun odağı C API'ları olmasına rağmen, bunları diğer dillerde kullanma yeteneğinizi etkileyen sorunlar memnuniyetle karşılanmaktadır.

Lütfen cevap başına bir kusur verin, böylece demokrasi cevapları sıralayabilir.


3
Joey, bu soru insanların nefret ettiği şeylerin bir listesini oluşturmayı isteyerek yapıcı olmama konusunda kararsız. Burada, yanıtların işaret ettikleri uygulamaların neden kötü olduğunu açıklar ve bunları nasıl geliştirecekleri hakkında ayrıntılı bilgi verirse, sorunun faydalı olma potansiyeli vardır . Bu amaçla, lütfen örneğinizi sorudan kendi cevabına taşıyın ve bunun neden bir sorun olduğunu / bir mallocd dizesinin onu nasıl düzelteceğini açıklayın . İlk cevapla iyi bir örnek oluşturmak bu sorunun gelişmesine gerçekten yardımcı olabilir. Teşekkürler!
Adam Lear

1
@Anna Lear: Sorumun neden sorunlu olduğunu söylediğin için teşekkürler . Bir örnek ve önerilen alternatif isteyerek yapıcı kalmaya çalışıyordum. Sanırım aklımdakileri göstermek için bazı örneklere ihtiyacım vardı.
Joey Adams

@Joey Adams Şuna bak. C API sorunlarını genel bir şekilde "otomatik olarak" çözmesi gereken bir soru soruyorsunuz. StackOverflow gibi siteler, programlama ile ilgili daha yaygın sorunların kolayca bulunabileceği VE yanıtlanacağı şekilde çalışmak üzere tasarlanmıştır. StackOverflow doğal olarak sorunuzun cevaplarının bir listesi ile sonuçlanacak, ancak daha yapılandırılmış, kolay aranabilir bir yolla sonuçlanacaktır.
Andrew T Finnell

Kendi sorumu kapatmaya oy verdim. Amacım, yeni C kütüphanelerine karşı bir kontrol listesi olarak kullanılabilecek bir cevaplar koleksiyonuna sahip olmaktı. Bu üç yanıtın hepsi "tutarsız", "mantıksız" veya "kafa karıştırıcı" gibi kelimeleri kullanıyor. Bir API'nin bu cevaplardan herhangi birini ihlal edip etmediğini objektif olarak belirleyemez.
Joey Adams

Yanıtlar:


5

Tutarsız veya mantıksız dönüş değerlerine sahip fonksiyonlar. İki iyi örnek:

1) HANDLE döndüren bazı windows işlevleri bir hata için NULL / 0 kullanır (CreateThread), bir hata için INVALID_HANDLE_VALUE / -1 kullanır (CreateFile).

2) 'time_t' imzalı veya imzasız bir tip olabileceğinden POSIX 'time' fonksiyonu hata durumunda '(time_t) -1' değerini döndürür.


2
Aslında, time_t (genellikle) imzalı. Ancak, 31 Aralık 1969'da "geçersiz" olarak adlandırmak oldukça mantıksızdır. Ben 60s kaba sanırım :-) Ciddiyetle, bir çözüm bir hata kodu döndürmek ve bir işaretçi ile sonucu geçmek olurdu, gibi: int time(time_t *out);ve BOOL CreateFile(LPCTSTR lpFileName, ..., HANDLE *out);.
Joey Adams

Kesinlikle. Time_t imzasız ise tuhaftır ve time_t imzalı ise, geçerli okyanusların ortasında bir kez geçersiz kılar .
David Schwartz

4

Açıklayıcı olmayan veya olumlu bir şekilde kafa karıştırıcı isimlere sahip işlevler veya parametreler. Örneğin:

1) CreateFile, Windows API'sinde aslında bir dosya oluşturmaz, bir dosya tanıtıcısı oluşturur. Bir parametre aracılığıyla istenirse, tıpkı 'açık' gibi bir dosya oluşturabilir. Bu parametre, adları anlamlarında ipucu bile olmayan 'CREATE_ALWAYS' ve 'CREATE_NEW' adlı değerlere sahiptir. ('CREATE_ALWAYS' dosya varsa başarısız olduğu anlamına mı geliyor yoksa üstte yeni bir dosya mı oluşturuyor? 'CREATE_NEW' her zaman yeni bir dosya oluşturduğu ve dosya zaten mevcutsa başarısız olduğu anlamına mı geliyor? üstüne dosya?)

2) adına rağmen koşulsuz bir bekleme olan POSIX pthreads API'sındaki pthread_cond_wait .


1
Cond içinde pthread_cond_wait"şartlı bekle" anlamına gelmez. Bir koşul değişkeni beklediğiniz anlamına gelir .
Jonathon Reinhart

Doğru, bir koşul için koşulsuz bir bekleme .
David Schwartz

3

Silinen tanıtıcılar olarak arabirimden geçirilen opak türler. Sorun, elbette, derleyicinin kullanıcı kodunu doğru argüman türleri için kontrol edememesidir.

Bu, aşağıdakiler dahil ancak bunlarla sınırlı olmamak üzere çeşitli formlarda ve tatlarda gelir:

  • void* taciz

  • kullanarak intbir kaynak kolu olarak (örnek: CDI kütüphanesi)

  • dize olarak yazılan bağımsız değişkenler

Ne kadar farklı türler (= birbirinin yerine kullanılamazsa) aynı tür silinmiş türle eşleştirilirse, daha kötüdür. Tabii ki, çare basitçe (C örneği) çizgileri boyunca tipafe opak işaretçiler sağlamaktır:

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

Tutarsız ve genellikle hantal dize döndürme kurallarına sahip işlevler.

Örneğin, getcwd kullanıcı tarafından sağlanan bir arabellek ve boyutunu ister. Bu, bir uygulamanın geçerli dizin uzunluğu üzerinde rasgele bir sınır ayarlaması veya bunun gibi bir şey yapması gerektiği anlamına gelir ( CCAN'dan ):

 /* *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);
}

Benim çözümüm: malloced dizesini döndür. Basit, sağlam ve daha az verimli değil. Gömülü platformlar ve eski sistemler dışında mallocaslında oldukça hızlı.


4
Bu kötü uygulama demezdim, bu iyi uygulama derim. 1) O kadar yaygındır ki, hiçbir programcı buna şaşırmamalıdır. 2) Hafıza sızıntısı hatalarının sayısız olasılığını dışlayan arayana ayırmayı bırakır. 3) Statik olarak tahsis edilmiş tamponlarla uyumludur. 4) Fonksiyon uygulamasını daha temiz hale getirir, bazı matematiksel formülü hesaplayan bir fonksiyon, dinamik bellek tahsisi gibi tamamen ilgisiz bir şeyle ilgilenmemelidir. Ana sistemin daha temiz olduğunu düşünüyorsunuz, ancak işlev daha da karmaşıklaşıyor. 5) malloc birçok sistemde bile kullanılamaz.

@Lundin: Sorun şu ki, programcılar gereksiz sabit kodlanmış sınırlar oluşturuyorlar ve yapmamaları için çok uğraşmaları gerekiyor (yukarıdaki örneğe bakın). Şeyler için It yolunda gibi snprintf(buf, 32, "%d", n)(sürece kesinlikle 30'dan fazla olmayan çıkış uzunluğu öngörülebilir olduğu, intolduğu gerçekten sisteminizde büyük). Gerçekten de, malloc birçok sistemde mevcut değildir, ancak masaüstü ve sunucu ortamları için öyle ve gerçekten iyi çalışıyor.
Joey Adams

Ancak sorun, örneğinizdeki işlevin sabit kodlanmış sınırlar koymamasıdır. Bunun gibi kodlar yaygın bir uygulama değildir. Burada main, işlevin bilmesi gereken tampon uzunluğu hakkında bir şeyler bilir. Her şey kötü program tasarımını gösteriyor. Main, getcwd fonksiyonunun ne yaptığını bile bilmiyor gibi, bu yüzden bulmak için bazı "kaba kuvvet" tahsisi kullanıyor. Bir yerde getcwd'nin bulunduğu modül ile arayanın karıştırıldığı modül arasındaki arayüz. Bu, işlevleri çağırmanın kötü olduğu anlamına gelmez, aksine deneyim, zaten listelediğim nedenler için iyi olduğunu gösterir.

1

Bileşik veri türlerini değere göre alan / döndüren veya geri çağrı kullanan işlevler.

Adı geçen tür bir birleşim veya bit alanları içeriyorsa daha da kötüsü.

Bir C arayan açısından, bunlar aslında Tamam, ama gerekmedikçe C veya C ++ yazmıyorum, bu yüzden genellikle bir FFI aracılığıyla arıyorum. Çoğu FFI sendikaları veya bit alanlarını desteklemez ve bazıları (Haskell ve MLton gibi) değere göre aktarılan yapıları destekleyemez. By-value yapılarını işleyenler için, en azından Common Lisp ve LuaJIT yavaş yollara zorlanır - Lisp'in Ortak Yabancı İşlev Arayüzü libffi aracılığıyla yavaş bir çağrı yapmalıdır ve LuaJIT, JIT-çağrıyı içeren kod yolunu derlemeyi reddeder. Ana bilgisayarlara geri çağrılabilen işlevler, LuaJIT, Java ve Haskell'de yavaş yolları tetikler; LuaJIT böyle bir çağrıyı derleyemez.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.