Neden typesafe struct işaretçisi yerine, genel bir API'de yayın gerektiren bir opak “tanıtıcı” kullanılsın mı?


27

Genel API'sinin şuna benzeyen bir kitaplığı değerlendiriyorum:

libengine.h

/* Handle, used for all APIs */
typedef size_t enh;


/* Create new engine instance; result returned in handle */
int en_open(int mode, enh *handle);

/* Start an engine */
int en_start(enh handle);

/* Add a new hook to the engine; hook handle returned in h2 */
int en_add_hook(enh handle, int hooknum, enh *h2);

Bunun enh, birkaç farklı veri tipine ( motorlar ve kancalar ) bir tutamaç olarak kullanılan genel bir tutamaç olduğuna dikkat edin .

Dahili olarak, bu API'lerin çoğu elbette "tanıtıcıyı" yaptıkları bir iç yapıya dönüştürür malloc:

engine.c

struct engine
{
    // ... implementation details ...
};

int en_open(int mode, *enh handle)
{
    struct engine *en;

    en = malloc(sizeof(*en));
    if (!en)
        return -1;

    // ...initialization...

    *handle = (enh)en;
    return 0;
}

int en_start(enh handle)
{
    struct engine *en = (struct engine*)handle;

    return en->start(en);
}

Şahsen, typedefözellikle tip güvenliğini tehlikeye attığında, arkasındaki şeyleri saklamaktan nefret ediyorum . (Bir verilen enh, aslında neyi kastettiğini nasıl bilebilirim?)

Bu nedenle, aşağıdaki API değişikliğini önererek bir çekme isteği gönderdim ( uyması için tüm kütüphaneyi değiştirdikten sonra ):

libengine.h

struct engine;           /* Forward declaration */
typedef size_t hook_h;    /* Still a handle, for other reasons */


/* Create new engine instance, result returned in en */
int en_open(int mode, struct engine **en);

/* Start an engine */
int en_start(struct engine *en);

/* Add a new hook to the engine; hook handle returned in hh */
int en_add_hook(struct engine *en, int hooknum, hook_h *hh);

Tabii ki, bu dahili API uygulamalarının çok daha iyi görünmesini sağlar, döküntüleri ortadan kaldırır ve tüketici güvenliğine göre / türünü güvenliğini korur.

libengine.c

struct engine
{
    // ... implementation details ...
};

int en_open(int mode, struct engine **en)
{
    struct engine *_e;

    _e = malloc(sizeof(*_e));
    if (!_e)
        return -1;

    // ...initialization...

    *en = _e;
    return 0;
}

int en_start(struct engine *en)
{
    return en->start(en);
}

Bunu aşağıdaki nedenlerden dolayı tercih ediyorum:

Ancak, projenin sahibi çekme talebinde balyalandı (yeniden yazılmış):

Şahsen, açığa vurma fikrinden hoşlanmıyorum struct engine. Hala mevcut yolun daha temiz ve daha arkadaş canlısı olduğunu düşünüyorum.

Başlangıçta, kanca tanıtıcısı için başka bir veri türü kullandım, ancak sonra kullanıma geçmeye karar verdim enh, böylece her türlü tutamaç basit tutmak için aynı veri türünü paylaşıyor. Bu kafa karıştırıcıysa, kesinlikle başka bir veri türü kullanabiliriz.

Bakalım bu PR hakkında başkalarının ne düşündüğünü görelim.

Bu kütüphane şu anda özel bir beta aşamasındadır, bu nedenle endişelenecek çok fazla tüketici kodu yoktur (henüz). Ayrıca, isimleri biraz karıştırdım.


Bir opak tanıtıcısı, adlandırılmış bir opak yapıdan daha iyi nasıl olabilir?

Not: Bu soruyu kapatıldığı Kod İnceleme'de sordum .


1
Başlığı, sorunuzun özünü daha net ifade ettiğine inandığım bir şey için düzenledim. Yanlış yorumladıysam geri dönmekte özgürsün.
Ixrec

1
@ Ixrec Bu daha iyi, teşekkür ederim. Tüm soruyu yazdıktan sonra, iyi bir başlık bulmak için zihinsel kapasitem bitti.
Jonathon Reinhart

Yanıtlar:


33

“Basit olan daha iyidir” mantrası çok fazla dogmatik oldu. Diğer şeyleri zorlaştırıyorsa basit her zaman daha iyi değildir. Meclis basittir - her komut, üst düzey dil komutlarından çok daha basittir - ve yine de Assembly programları aynı şeyi yapan üst düzey dillerden daha karmaşıktır. Sizin durumunuzda, tek biçimli tutamaç tipi enh, işlevleri karmaşık hale getirme pahasına türleri basitleştirir. Genelde projenin türleri, fonksiyonlarına göre alt-doğrusal hızda büyüme eğiliminde olduğundan, proje büyüdükçe, işlevleri daha basit hale getirebilirse, genellikle daha karmaşık türleri tercih edersiniz - bu nedenle yaklaşımınız doğru gibi görünmektedir.

Projenin yazarı, yaklaşımınızın " açığa vurmasıstruct engine " konusunda endişeli . Onlara yapının kendisini göstermediğini - sadece bir yapı olduğu gerçeğini açıklardım engine. Kütüphanenin kullanıcısının zaten bu tipin farkında olması gerekir - örneğin, ilk argümanın en_add_hooko tip olduğunu ve ilk argümanın farklı bir tip olduğunu bilmeleri gerekir . Bu yüzden aslında API'yi daha karmaşık hale getirir, çünkü fonksiyonun "imzası" belgesini bu türleri belgelemek yerine, başka bir yerde belgelendirilmesi gerekir ve derleyici programcı türlerini artık doğrulayamadığından.

Dikkat edilmesi gereken bir şey - yeni API'niz kullanıcı kodunu yazmak yerine biraz daha karmaşık hale getirir:

enh en;
en_open(ENGINE_MODE_1, &en);

Şimdi tanıtıcılarını bildirmek için daha karmaşık bir sözdizimine ihtiyaçları var:

struct engine* en;
en_open(ENGINE_MODE_1, &en);

Bununla birlikte, çözüm oldukça basittir:

struct _engine;
typedef struct _engine* engine

ve şimdi doğrudan yazabilirsiniz:

engine en;
en_open(ENGINE_MODE_1, &en);

Kütüphanenin , benim de takip ettiğim gibi olan Linux Kodlama Stili'ne uyduğunu iddia ettiğini söylemeyi unuttum . Orada, sadece yazmayı önlemek için yapıların tanımlanmasının structaçıkça tavsiye edilmediğini göreceksiniz .
Jonathon Reinhart

@JonathonReinhart , yapının kendisini yapılandırmamak için işaretçiyi yazarak tanımlamaktadır .
cırcır ucube

@JonathonReinhart ve aslında bu bağlantıyı okuduğumda "tamamen opak nesneler" için izin verildiğini görüyorum. (bölüm 5 kuralı a)
cırcır ucube

Evet, ancak istisnai olarak nadir durumlarda. Dürüst olmak gerekirse, pte typedefs ile başa çıkmak için tüm mm kodunu yeniden yazmaktan kaçınmak için eklendiğine inanıyorum. Döndürme kilit koduna bakın. Tamamen arch'a özgüdür (ortak veri yoktur), ancak asla bir typedef kullanmazlar.
Jonathon Reinhart

8
Ben tercih eder typedef struct engine engine;ve kullanırdım engine*: Bir az isim daha tanıtıldı ve bu onun bir tutamacı olduğunu açıkça gösteriyor FILE*.
Deduplicator

16

Burada iki tarafta da bir karışıklık var gibi görünüyor:

  • bir tanıtıcı yaklaşımı kullanmak, tüm tanıtıcılar için tek bir tanıtıcı türü kullanmayı gerektirmez
  • açığa çıkarmak struct Ayrıntılarını göstermiyor adı (sadece varlığı)

İşaretçi yerine, işaretçilerin doğrudan yönlendirilmesine izin verirken (işaretçiler dahil free) doğrudan işaretleme yapılmasına izin verirken, işaretçilerin yerine doğrudan işaretçiler yerine, C gibi bir dilde tutamaçların kullanılmasının avantajları vardır. .

Ancak, tek bir tanıtıcı türüne sahip olma yaklaşımı, typedef güvenli değildir ve çok fazla kedere neden olabilir.

Bu yüzden benim kişisel önerim, ikinizi de tatmin edebileceğini düşündüğüm güvenli tip tutamaçlara doğru ilerlemektir. Bu oldukça basit bir şekilde gerçekleştirilir:

typedef struct {
    size_t id;
} enh;

typedef struct {
    size_t id;
} oth;

Şimdi, kimse 2bir sap olarak kazara geçemez , ne de kazara bir sapı motora bir sapın beklendiği süpürgeye geçiremez.


Bu yüzden, aşağıdaki API değişikliğini önermek için bir çekme isteği gönderdim ( uyması için tüm kütüphaneyi değiştirdikten sonra )

Bu senin hatadır: Bir açık kaynak kitaplığı önemli işlere girmekten önce, kontak yazar (lar) / sürdürücü (lar) değişiklik görüşmek üzere ayarlıyoruz . Her ikinizin de ne yapacağınız (ya da yapmayacağınız) konusunda hemfikir olmanıza, gereksiz işlerden ve ondan kaynaklanan sıkıntılardan kaçınmanıza izin verecektir.


1
Teşekkür ederim. Yine de tutamaçlarla ne yapacağına girmedin . Tipik bir tanımla bile olsa, işaretçilerin hiçbir zaman gösterilmediği bir tanıtıcı tabanlı API uygulamıştım . Her API çağrısı girişinde bir veri ~ pahalı arama dahil - Linux bakar çok yolu gibi struct filebir mesafede int fd. Bu, bir kullanıcı modu kitaplığı IMO için kesinlikle geçersizdir.
Jonathon Reinhart

@JonathonReinhart: Kütüphane zaten kullanım alanı sağladığından, genişleme gereğini hissetmedim. Gerçekten de, işaretçiyi tam sayıya dönüştürmekten, bir "havuz" a sahip olmaktan ve ID'leri anahtar olarak kullanmaktan çok sayıda yaklaşım vardır. Yaklaşımı Debug (doğrulama için ID + araması) ve Serbest Bırakma (hız için sadece dönüştürülmüş gösterici) arasında bile değiştirebilirsiniz .
Matthieu M.

Tamsayı tablo indeksinin yeniden kullanılması aslında bir nesnenin (indeks ) serbest bırakıldığı ABA probleminden zarar görecek 3, daha sonra yeni bir nesne yaratılacak ve ne yazık ki 3tekrar indeks atanacak . Basitçe söylemek gerekirse, API tasarımının açık bir bölümünde referans sayımı (nesnelerle paylaşılan mülkiyete ilişkin sözleşmelerle birlikte) yapılmadığı sürece, C'de güvenli nesne ömrü mekanizmasına sahip olmak zordur.
15’te

2
@ rwong: Bu sadece saf düzende bir sorun; örneğin bir eski çağn sayacını kolayca tümleştirebilirsiniz, böylece eski bir tanıtıcı belirtildiğinde, bir çağ uyuşmazlığı elde edersiniz.
Matthieu M.

1
@JonathonReinhart önerisi: tartışmayı daha önemli hususlara yönlendirmek için sorunuzda "katı diğer adlandırma kuralından" bahsedebilirsiniz.
rwong,

3

Opak tutamacın gerekli olduğu bir durum;

struct SimpleEngine {
    int type;  // always SimpleEngine.type = 1
    int a;
};

struct ComplexEngine {
    int type;  // always ComplexEngine.type = 2
    int a, b, c;
};

int en_start(enh handle) {
    switch(*(int*)handle) {
    case 1:
        // treat handle as SimpleEngine
        return start_simple_engine(handle);
    case 2:
        // treat handle as ComplexEngine
        return start_complex_engine(handle);
    }
}

Kütüphane, alanların aynı başlık kısmına sahip iki veya daha fazla yapı tipine sahip olduğunda, yukarıdaki "tip" gibi, bu yapı tiplerinin ortak bir ana yapıya sahip olduğu düşünülebilir (C ++ 'daki temel sınıf gibi).

Başlık bölümünü "yapı motoru" olarak tanımlayabilirsiniz, bunun gibi;

struct engine {
    int type;
};

struct SimpleEngine {
    struct engine base;
    int a;
};

struct ComplexEngine {
    struct engine base;
    int a, b, c;
};

int en_start(struct engine *en) { ... }

Ancak bu isteğe bağlı bir karardır, çünkü yapı motoru kullanılmasına bakılmaksızın tip atmalarına ihtiyaç vardır.

Sonuç

Bazı durumlarda, opak tutamaçların opak olarak adlandırılmış yapılar yerine kullanılmasının nedenleri vardır.


Sendika kullanmanın tehlikeli atma yerine bunu daha güvenli hale getirdiğini düşünüyorum. Tamamen bir örnek göstererek bir araya getirdiğim bu özete göz atın .
Jonathon Reinhart

Ama aslında, switchilk etapta kaçınmak, "sanal işlevleri" kullanarak muhtemelen idealdir ve tüm sorunu çözer.
Jonathon Reinhart

Oyundaki tasarımınız önerdiğimden daha karmaşık. Şüphesiz, daha az döküm, güvenli ve akıllı hale getirir, ancak daha fazla kod ve tür sunar. Bence tip güvenli olamayacak kadar zor görünüyor. Ben, belki de kütüphane yazarı tür güvenliği yerine KISS'i izlemeye karar verdim .
Akio Takahashi

Peki, gerçekten basit tutmak istiyorsanız, hata kontrolünü de tamamen göz ardı edebilirsiniz!
Jonathon Reinhart

Benim düşünceme göre, tasarım basitliği bir miktar hata kontrolünden daha çok tercih edilir. Bu durumda, bu tür hata kontrolleri yalnızca API işlevlerinde mevcuttur. Dahası, birleşim kullanarak tür yayınlarını kaldırabilirsiniz, ancak birliğin doğal olarak türden güvensiz olduğunu unutmayın.
Akio Takahashi

2

İşleç yaklaşımının en belirgin yararı, dış yapıyı dış API'yı bozmadan değiştirebilmenizdir. Verilen, hala istemci yazılımını değiştirmek zorundasınız, ama en azından arayüzü değiştirmiyorsunuz.

Yaptığı diğer şey, her biri için açık bir API arayüzü sağlamak zorunda kalmadan, çalışma zamanında birçok farklı olası tip arasından seçim yapma yeteneği sağlamak. Her uygulamanın biraz farklı olduğu ve biraz farklı veriler ürettiği birkaç farklı sensör türünden sensör okumaları gibi bazı uygulamalar bu yaklaşıma iyi yanıt verir.

Yapıları yine de müşterilerinize sağlayacağınız için, döküm gerektiren çok daha basit bir API için biraz çalışma güvenliği için (çalışma zamanında hala kontrol edilebilir) biraz taviz veriyorsunuz.


5
"İç yapıları değiştirmeden değiştirebilirsiniz .." - ayrıca ileriye dönük bildirim yaklaşımını da kullanabilirsiniz.
kullanıcı253751

"İleriye dönük bildirim" yaklaşımı hala tür imzalarını bildirmenizi gerektirmiyor mu? Ve eğer yapıları değiştirirseniz, bu tip imzalar hala değişmiyor mu?
Robert Harvey,

İleriye dönük bildirim yalnızca türün adını bildirmenizi gerektirir - yapısı gizli kalır.
Idan Arye

Öyleyse, tür yapısını bile uygulamazsa, ileriye dönük bildirimin faydası ne olur?
Robert Harvey,

6
@RobertHarvey Unutma - bu konuştuğumuz C. Hiçbir yöntem yoktur, bu nedenle ad ve yapı dışında türden başka bir şey yoktur. O Eğer yaptığımız yapıyı güçlendirmek düzenli beyanı ile aynı olurdu. Yapıyı zorlamadan adı göstermenin amacı, bu işareti işlev imzalarında kullanabilmenizdir. Tabi ki, yapı olmadan sadece tipe işaretçiler kullanabilirsiniz, çünkü derleyici boyutlarını bilemez, ancak işaretçiler kullanılarak C içinde dolaylı işaretçi dökümü olmadığından, statik tip yazarak sizi koruyacak kadar iyidir.
Idan Arye

2

Déjà vu

Bir opak tanıtıcısı, adlandırılmış bir opak yapıdan daha iyi nasıl olabilir?

Tam olarak aynı senaryo ile karşılaştım , sadece bazı ince farklarla. SDK'mızda bunun gibi birçok şey vardı:

typedef void* SomeHandle;

Benim önerim, iç tiplerimizle eşleşmesini sağlamaktı:

typedef struct SomeVertex* SomeHandle;

SDK'yı kullanan üçüncü şahıslar için, hiçbir önemi yoktur. Opak bir tip. Kimin umrunda? ABI * veya kaynak uyumluluğu üzerinde etkisi yoktur ve SDK'nın yeni sürümlerini kullanmak, eklentinin yine de derlenmesini gerektirir.

* Not: Gnasher'ın belirttiği gibi, işaretçi yapılandırmak ve geçersiz kılmak gibi bir şeyin boyutunun aslında ABI'yi etkileyeceği farklı bir boyut olabileceği durumlar olabilir. Onun gibi, pratikte hiç karşılaşmadım. Ancak, bu açıdan, ikincisi bazı belirsiz bağlamlarda taşınabilirliği geliştirebilir, bu yüzden çoğu insan için muhtemelen tartışmasız da ikincisini tercih etmek için başka bir neden budur.

Üçüncü Parti Hatalar

Ayrıca, iç gelişme / hata ayıklama için tür güvenliğinden daha fazla nedenim vardı. İki benzer tutamaç ( Panelve diğer bir PanelNewdeyişle) her ikisi de void*tutamaçları için bir typedef kullandığı için kodlarında hataları olan bir dizi eklenti geliştiricisine sahip olduk ve yanlışlıkla yanlış tutamaçlardan sadece yanlış kullanım alanlarına geçtiler void*herşey için. Bu yüzden aslında kullananların tarafında hatalara neden oldu. SDK. Böylelikle böcekleri iç geliştirme ekibine de çok pahalıya mal oldu, çünkü SDK'mızdaki hatalardan şikayetçi hata raporları gönderiyorlardı ve eklentiyi hata ayıklamak zorunda kaldık ve bunun eklentinin yanlış tanıtıcılardan geçen bir hatadan kaynaklandığını bulduk. kolayca hatta bir uyarı olmadan izin verilir yanlış yerlere (her tanıtıcı zamanlar için bir takma void*veyasize_t ). Biz gereksiz çünkü tüm dahili bilgileri, hatta sırf uzak gizleme kavramsal saflık için bizim isteğiyle kendilerine düşen kaynaklanan hatalardan üçüncü taraflar için bir hata ayıklama hizmet veren bizim vakit edildi Yani isimlerini iç ait structs.

Typedef'i Tutmak

Fark biz sopa yapmasını teklif olmasıdır typedefhala almamayı istemciler yazma olacağını gelecek eklentisi sürümlerin kaynak uyumluluğunu etkiler. Ben şahsen C'den yazarak tanımlamama fikrini sevmeme rağmen , SDK perspektifindenstruct SomeVertexstructtypedef bütün mesele opaklık olduğu için yardımcı olabilir. Bu yüzden, sadece herkese açık API için bu standardın dinlendirilmesini öneriyorum. SDK kullanan istemciler için, bir tanıtıcının yapı, tam sayı vb. İçin işaretçi olup olmadığının önemi olmamalıdır. Yanlış sapta yanlış yere yanlış yere geçin.

Tip Bilgisi

Dökümden kaçınmanın en önemli olduğu şey, sizin içinizdir, içseller. Tüm dahili isimleri SDK'dan gizlemenin bu tür bir estetiği, tüm tip bilgisini kaybetmenin önemli bir bedeli olan ve kritik bilgileri almak için hata ayıklayıcılarımıza gereksiz yere serpmemizi gerektiren bazı kavramsal estetiktir. Bir C programcısı büyük ölçüde C de buna alışmış olsa da, buna ihtiyaç duymak da sadece sorun istiyor.

Kavramsal idealler

Genel olarak, pratik, günlük ihtiyaçların çok üstünde bir miktar kavramsal saflık fikri ortaya koyan geliştiricilere dikkat etmek istersiniz. Bunlar, bazı Ütopyacı idealini aramak için kod üssünüzün korunmasını sürdürebilir, tüm ekibin doğal olmadığı ve mürettebatın yarısı cilt kanserinden ölürken D vitamini eksikliğine neden olabileceği korkusundaki bir çölde güneş kremi yaşamasını önler.

Kullanıcı Sonu Tercihi

API kullananların kesin kullanıcı sonu bakış açısına rağmen, iyi çalışan bir API veya daha iyi sonuç veren, ancak karşılığında değer veremeyecekleri bir isim ortaya koyan bir API mı tercih ederler? Çünkü bu pratik bir denge. Genel bir bağlamın dışında gereksiz yere bilgi kaybı, hata riskini artırıyor ve takım boyu geniş bir ortamda büyük çaplı bir kod tabanında birkaç yıl boyunca Murphy kanunları oldukça uygulanabilir. Hatalı bir şekilde böcek riskini artırırsanız, şansınız en azından biraz daha fazla böcek elde edersiniz. Büyük bir ekip ortamında, akla gelebilecek her türlü insan hatasının sonunda potansiyelden gerçeğe dönüşeceğini bulmak çok uzun sürmez.

Bu yüzden belki de kullanıcılara yöneltilecek bir soru. "Daha önce hiç umursayamayacağınız bazı iç opak isimleri ortaya çıkaran daha sert bir SDK'yı mı tercih edersiniz?" Ve eğer bu soru yanlış bir ikilik ortaya koyuyorsa, çok daha büyük bir ortamda daha fazla ekip çapında deneyime ihtiyaç duyulduğunu söyleyebilirim. Böylelikle böcekler için daha yüksek bir riskin uzun vadede gerçek böcekleri tezahür ettireceği gerçeğini takdir etmek gerekir. Bu, geliştiricinin böceklerden kaçınmak konusunda ne kadar güvendiği konusunda önemli değildir. Ekip çapında bir ortamda, en zayıf bağlantılar ve en azından en kolay ve en hızlı yollardan açılmalarını düşünmek için yardımcı olur.

öneri

Bu yüzden burada size hala tüm hata ayıklama avantajlarını elde etme imkanı verecek bir uzlaşma öneriyorum:

typedef struct engine* enh;

... uzak yazım pahasına olsa bile, bu structbizi gerçekten öldürecek mi? Muhtemelen hayır, ben de sizin tarafınızdan bazı pragmatizm önerebilirim, ama dahası, size_tburada kullanarak ve katlanarak daha iyi bir neden olmadan zaten tam olan bilgiyi gizlemek dışında, tamsayıdan / tamsayıya döküm yapmaktan daha fazla hata ayıklamayı tercih eder. % Kullanıcıya gizlenmiş ve muhtemelen bundan daha fazla zarar veremez size_t.


1
Bu çok küçük bir fark: C Standardına göre, tüm "pointer to struct" ifadesi aynı gösterime sahip, bu yüzden tüm "pointer birliği" de yap, "void *" ve "char *" yap, ama bir void * ve "pointer" yapı "farklı sizeof () ve / veya farklı gösterime sahip olabilir. Uygulamada, bunu hiç görmedim.
gnasher729

@ gnasher729 Aynı şekilde, belki de bu parçayı gereksiz yere dökmekten kaçınmak için başka bir sebep olarak döküm void*veya size_tgeri almadaki taşınabilirlik kaybına ilişkin olarak nitelemeliyim . Ben de bunu göz ardı ettim, çünkü hedeflediğimiz platformlar (her zaman masaüstü platformlarıydı: linux, OSX, Windows).


1

Asıl nedenin atalet olduğundan şüpheleniyorum, her zaman yaptıkları şey buydu ve işe yarıyor, neden değişti?

Görebilmemin ana nedeni, opak tutamacın tasarımcının sadece bir yapıya değil, arkasına bir şey koymasına izin vermesidir. API birden fazla opak tip döndürür ve kabul ederse hepsi arayan kişiye aynı görünür ve ince baskı değişirse hiçbir zaman derleme problemi veya yeniden derleme gerekmez. En_NewFlidgetTwiddler (handle ** newTwiddler) bir işaretçiyi bir tanıtıcı yerine Twiddler'a döndürmek üzere değişirse, API değişmez ve herhangi bir yeni kod sessizce bir tanıtıcı kullanmadan önce bir işaretçiyi kullanır. Ayrıca, işletim sistemi tehlikesi veya herhangi bir şeyin sessizce sınırları geçerse imleci "sabitleme" tehlikesi yoktur.

Bunun dezavantajı, elbette, arayan kişinin içine her şeyi besleyebilmesidir. 64 bit bir şeyin var mı? API çağrısında 64 bit yuvasına yerleştirin ve ne olduğunu görün.

en_TwiddleFlidget(engine, twiddler, flidget)
en_TwiddleFlidget(engine, flidget, twiddler)

Her ikisi de derlenir ama bahse girerim onlardan biri istediğini yapar.


1

Tutumun, bir C kütüphane API'sini yeni başlayanlar tarafından kötüye kullanıma karşı savunmak için uzun zamandır devam eden bir felsefeden kaynaklandığını düşünüyorum.

Özellikle,

  • Kütüphane yazarları yapıya bir işaretçi olduğunu bilir ve yapı detayları kütüphane koduna görünür.
  • Kütüphaneyi kullanan tüm deneyimli programcılar da bunun bazı opak yapıların bir göstergesi olduğunu bilir;
    • Bu yapılarda depolanan baytları karıştırmamak için yeterince acı verici bir deneyime sahipti .
  • Deneyimsiz programcılar da bilmiyor.
    • memcpyOpak veriyi deneyecekler veya yapı içindeki baytları veya kelimeleri arttıracaklardır . Hack git.

Uzun süredir geleneksel karşı önlem:

  • Bir opak tutamacının aslında aynı işlem-bellek-alanında varolan bir opak yapıya bir işaretçi olduğu gerçeğini maskelemek.
    • Bunu iddia etmekle, a ile aynı sayıda bit içeren bir tamsayı değerdir. void*
    • Ekstra çevresel olmak için, işaretçinin bitlerini de masquerade edin, örn.
      struct engine* peng = (struct engine*)((size_t)enh ^ enh_magic_number);

Bu sadece uzun geleneklere sahip olduğunu söylemek; Doğru mu yanlış mı olduğuna dair hiçbir fikrim yoktu.


3
Gülünç xor dışında, benim çözümüm de bu güvenliği sağlıyor. Müşteri, yapının büyüklüğünden veya içeriğinden habersiz kalmaya devam eder, ek güvenlik türü avantajı sağlar. Bir işaretçi tutmak için bir size_t kötüye kullanma daha iyi olduğunu anlamıyorum.
Jonathon Reinhart

@JonathonReinhart, istemcinin yapıdan habersiz olma olasılığı çok düşüktür. Soru daha fazlası: yapıyı alabilirler ve değiştirilmiş bir versiyonu kütüphanenize geri verebilirler. Sadece açık kaynaklı değil, daha genel olarak. Çözüm, aptal XOR değil modern bellek bölümlemesidir.
Móż

Neden bahsediyorsun? Tek söylediğim, bir işaretleyiciyi söz konusu yapıya yönlendirmeye çalışan herhangi bir kodu derleyemeyeceğiniz veya boyutu hakkında bilgi gerektiren herhangi bir şeyi yapamayacağınızdır. Tabii ki, eğer gerçekten istersen, işlemin tamamında öbek tutabilirsin (, 0,).
Jonathon Reinhart

6
Bu argüman Machiavelli'ye karşı korunmak gibi görünüyor . Kullanıcı API’ime çöp atmak istiyorsa , onları durduramam mümkün değil. Bunun gibi güvenli olmayan bir arabirim sunmak, aslında API'nin yanlışlıkla suistimal edilmesini kolaylaştırdığından bu konuda pek yardımcı olmaz.
ComicSansMS

@ComicSansMS, burada gerçekten engellemeye çalıştığım bu olduğundan "kazayla" bahsettiğiniz için teşekkür ederiz .
Jonathon Reinhart
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.