एक अपारदर्शी "हैंडल" का उपयोग क्यों करें, जिसे टाइपसी स्ट्रक्चर पॉइंटर के बजाय सार्वजनिक एपीआई में कास्टिंग की आवश्यकता होती है?


27

मैं एक ऐसे पुस्तकालय का मूल्यांकन कर रहा हूँ जिसका सार्वजनिक एपीआई वर्तमान में इस तरह दिखता है:

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);

ध्यान दें कि enhएक सामान्य संभाल है, जिसे कई अलग-अलग डेटाटिप्स ( इंजन और हुक ) के हैंडल के रूप में उपयोग किया जाता है ।

आंतरिक रूप से, इनमें से अधिकांश APIs ने "हैंडल" को एक आंतरिक संरचना में डाला है, जो उन्होंने malloc'd'

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

निजी तौर पर, मैं typedefएस के पीछे छिपी चीजों से नफरत करता हूं , खासकर जब यह प्रकार की सुरक्षा से समझौता करता है। (यह देखते हुए enh, मुझे कैसे पता चलेगा कि यह वास्तव में क्या है?)

इसलिए मैंने एक पुल निवेदन प्रस्तुत किया, जिसमें निम्नलिखित एपीआई परिवर्तन का सुझाव दिया गया ( पूरे पुस्तकालय को संशोधित करने के बाद ):

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);

बेशक, यह आंतरिक एपीआई कार्यान्वयन को बेहतर बनाता है, जातियों को समाप्त करता है, और उपभोक्ता के दृष्टिकोण से / के लिए प्रकार की सुरक्षा बनाए रखता है।

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

मैं निम्नलिखित कारणों से इसे पसंद करता हूं:

हालाँकि, परियोजना के मालिक ने पुल अनुरोध (पैराफ्रास्ड) पर जोर दिया:

व्यक्तिगत रूप से मुझे उजागर करने का विचार पसंद नहीं है struct engine। मुझे अभी भी लगता है कि वर्तमान तरीका क्लीनर और अधिक अनुकूल है।

प्रारंभ में मैंने हुक हैंडल के लिए एक और डेटा प्रकार का उपयोग किया, लेकिन फिर उपयोग करने के लिए स्विच करने का निर्णय लिया enh, इसलिए सभी प्रकार के हैंडल समान डेटा प्रकार को सरल रखने के लिए साझा करते हैं। यदि यह भ्रामक है, तो हम निश्चित रूप से दूसरे डेटा प्रकार का उपयोग कर सकते हैं।

आइए देखें कि अन्य लोग इस पीआर के बारे में क्या सोचते हैं।

यह लाइब्रेरी वर्तमान में एक निजी बीटा चरण में है, इसलिए इसके बारे में (अभी तक) चिंता करने के लिए बहुत अधिक उपभोक्ता कोड नहीं है। इसके अलावा, मैंने नामों को थोड़ा आगे बढ़ाया है।


नाम, अपारदर्शी संरचना की तुलना में एक अपारदर्शी संभाल कैसे बेहतर है?

नोट: मैंने यह सवाल कोड रिव्यू में पूछा था, जहां इसे बंद कर दिया गया था।


1
मैंने शीर्षक को कुछ इस तरह संपादित किया है कि मेरा मानना ​​है कि आपके प्रश्न के मूल को अधिक स्पष्ट रूप से व्यक्त करता है। अगर मैंने इसकी गलत व्याख्या की है तो इसे वापस लेने के लिए स्वतंत्र महसूस करें।
Ixrec

1
@Ixrec यह बेहतर है, धन्यवाद। पूरा प्रश्न लिखने के बाद, मैं एक अच्छी उपाधि के साथ आने की मानसिक क्षमता से बाहर चला गया।
जोनाथन रेनहार्ट

जवाबों:


33

"सरल बेहतर है" मंत्र बहुत अधिक हठधर्मी हो गया है। सरल हमेशा बेहतर नहीं होता है अगर यह अन्य चीजों को जटिल करता है। असेंबली सरल है - प्रत्येक कमांड उच्च-स्तरीय भाषाओं के कमांड की तुलना में बहुत सरल है - और फिर भी असेंबली प्रोग्राम उच्च-स्तरीय भाषाओं की तुलना में अधिक जटिल हैं जो समान कार्य करते हैं। आपके मामले में, यूनिफ़ॉर्म हैंडल प्रकार enhफ़ंक्शन को जटिल बनाने की कीमत पर सरल बनाता है। चूँकि आमतौर पर प्रोजेक्ट के प्रकार फंक्शन्स की तुलना में सब-लीनियर रेट में बढ़ते हैं, क्योंकि प्रोजेक्ट बड़ा हो जाता है इसलिए आप आमतौर पर अधिक जटिल प्रकारों को प्राथमिकता देते हैं यदि यह फ़ंक्शन को सरल बना सकता है - तो इस संबंध में आपका दृष्टिकोण सही लगता है।

प्रोजेक्ट का लेखक चिंतित है कि आपका दृष्टिकोण " एक्सपोज़िंगstruct engine " है। मैंने उन्हें समझाया होगा कि यह संरचना को उजागर नहीं कर रहा है - केवल तथ्य यह है कि नाम की एक संरचना है engine। पुस्तकालय के उपयोगकर्ता को पहले से ही उस प्रकार के बारे में पता होना चाहिए - उन्हें जानने की आवश्यकता है, उदाहरण के लिए, कि पहला तर्क en_add_hookउस प्रकार का है, और पहला तर्क एक अलग प्रकार का है। तो यह वास्तव में एपीआई को अधिक जटिल बनाता है, क्योंकि फ़ंक्शन के "हस्ताक्षर" दस्तावेज़ होने के बजाय इन प्रकारों को इसे कहीं और प्रलेखित करने की आवश्यकता होती है, और क्योंकि कंपाइलर अब प्रोग्रामर के प्रकारों को सत्यापित नहीं कर सकता है।

एक बात जो नोट की जानी चाहिए - आपका नया एपीआई उपयोगकर्ता कोड को लिखने के बजाय थोड़ा और अधिक जटिल बनाता है:

enh en;
en_open(ENGINE_MODE_1, &en);

उन्हें अब अपने हैंडल को घोषित करने के लिए अधिक जटिल वाक्यविन्यास की आवश्यकता है:

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

समाधान, हालांकि, काफी सरल है:

struct _engine;
typedef struct _engine* engine

और अब आप सीधे लिख सकते हैं:

engine en;
en_open(ENGINE_MODE_1, &en);

मैं यह उल्लेख करना भूल गया कि पुस्तकालय लिनक्स कोडिंग स्टाइल का पालन करने का दावा करता है , जो कि जैसा मैं करता हूं वैसा ही होता है। वहाँ, आप देखेंगे कि केवल लिखने से बचने के लिए टंकण संरचनाएँ structस्पष्ट रूप से हतोत्साहित हैं।
जोनाथन रेनहार्ट

@JonathonReinhart वह पॉइंटर को टाइप करने के लिए लिख रहा है न कि स्ट्रक्चर को।
शाफ़्ट फ्रीक

@JonathonReinhart और वास्तव में उस लिंक को पढ़ने पर मुझे लगता है कि "पूरी तरह से अपारदर्शी वस्तुओं" के लिए इसकी अनुमति है। (अध्याय 5 का नियम)
शाफ़्ट सनकी

हां, लेकिन केवल असाधारण मामलों में। मुझे विश्वास है कि पीटीई टाइपसीफ से निपटने के लिए सभी मिमी कोड को फिर से लिखने से बचने के लिए जोड़ा गया था। स्पिन लॉक कोड को देखें। यह पूरी तरह से आर्क विशिष्ट (कोई सामान्य डेटा नहीं) है, लेकिन वे कभी भी एक टाइफेड का उपयोग नहीं करते हैं।
जोनाथन रेनहार्ट

8
मैं पसंद करूंगा typedef struct engine engine;और उपयोग करूंगा engine*: एक कम नाम शुरू किया गया है, और यह स्पष्ट करता है कि यह एक हैंडल जैसा है FILE*
डेडुप्लिकेटर

16

यहाँ दोनों पक्षों में एक भ्रम की स्थिति प्रतीत होती है:

  • एक हैंडल दृष्टिकोण का उपयोग करने से सभी हैंडल के लिए एकल हैंडल प्रकार का उपयोग करने की आवश्यकता नहीं होती है
  • structनाम उजागर करने से उसका विवरण उजागर नहीं होता (केवल उसका अस्तित्व)

सी जैसी भाषा में, नंगे पॉइंटर्स के बजाय हैंडल का उपयोग करने के फायदे हैं, क्योंकि पॉइंटर को सौंपने से पॉइंटर को सीधे हेरफेर करने की अनुमति मिलती है (कॉल सहित free) जबकि एक हैंडल को सौंपने के लिए क्लाइंट को एपीआई के माध्यम से किसी भी कार्रवाई को करने की आवश्यकता होती है ।

हालाँकि, एक प्रकार के हैंडल होने का दृष्टिकोण, एक के माध्यम से परिभाषित typedefनहीं है, जो सुरक्षित नहीं है, और बहुत सारे दुःख का कारण बन सकता है।

मेरा व्यक्तिगत सुझाव इस प्रकार प्रकार के सुरक्षित हैंडल की ओर बढ़ना होगा, जो मुझे लगता है कि आप दोनों को संतुष्ट करेगा। यह केवल निपुण है:

typedef struct {
    size_t id;
} enh;

typedef struct {
    size_t id;
} oth;

अब, कोई गलती से 2एक हैंडल के रूप में पारित नहीं कर सकता है और न ही गलती से एक ब्रूमस्टिक के लिए एक हैंडल पास कर सकता है जहां इंजन के लिए एक हैंडल की उम्मीद है।


इसलिए मैंने एक पुल अनुरोध प्रस्तुत किया, जिसमें निम्नलिखित एपीआई परिवर्तन का सुझाव दिया गया था ( पूरे पुस्तकालय को संशोधित करने के बाद इसे अनुरूप करने के लिए)

यही कारण है कि अपनी गलती है: एक खुला स्रोत पुस्तकालय पर महत्वपूर्ण काम में उलझाने से पहले, संपर्क लेखक (ओं) / मेंटेनर (रों) परिवर्तन पर चर्चा के लिए अग्रिम । यह आप दोनों को क्या करने (या न करने) पर सहमत होने की अनुमति देगा, और अनावश्यक काम और उस से होने वाली हताशा से बचें।


1
धन्यवाद। आप हालांकि हैंडल के साथ क्या करने वाले हैं । मैंने एक वास्तविक संभाल आधारित एपीआई को लागू किया है, जहां पॉइंटर्स कभी भी उजागर नहीं होते हैं, भले ही एक टंकण के माध्यम से। इसमें हर API कॉल के प्रवेश पर डेटा का एक ~ महंगा लुकअप शामिल था - जिस तरह से लिनक्स struct fileएक से दिखता है int fd। यह निश्चित रूप से एक उपयोगकर्ता-मोड लाइब्रेरी IMO के लिए ओवरकिल है।
जोनाथन रेइनहर्ट

@JonathonReinhart: ठीक है, क्योंकि पुस्तकालय पहले से ही हैंडल प्रदान करता है, मुझे विस्तार करने की आवश्यकता महसूस नहीं हुई। वास्तव में कई दृष्टिकोण हैं, बस सूचक को पूर्णांक में परिवर्तित करने से लेकर "पूल" और आईडी के रूप में कुंजी का उपयोग करने के लिए। आप डिबग (आईडी + लुकअप, सत्यापन के लिए) और रिलीज़ (बस परिवर्तित सूचक, गति के बीच) के बीच दृष्टिकोण को भी बदल सकते हैं ।
मैथ्यू एम।

पूर्णांक तालिका सूचकांक का पुन: उपयोग वास्तव में एबीए समस्या से ग्रस्त होगा , जहां एक वस्तु (सूचकांक 3) को मुक्त किया जाता है, फिर एक नई वस्तु बनाई जाती है और दुर्भाग्य 3से फिर से सूचकांक सौंपा जा रहा है । सीधे शब्दों में कहें तो C में सुरक्षित ऑब्जेक्ट लाइफटाइम मैकेनिज्म होना मुश्किल है जब तक कि रेफरेंस काउंटिंग (वस्तुओं के लिए साझा मालिकाना हक के साथ) एपीआई डिजाइन के स्पष्ट हिस्से में बना हो।
rwong

2
@ श्रंग: यह केवल भोली योजना में एक मुद्दा है; आप आसानी से एक युगांतर काउंटर को एकीकृत कर सकते हैं, उदाहरण के लिए, ताकि जब एक पुराना हैंडल निर्दिष्ट किया जाए, तो आपको एक युग बेमेल मिल जाएगा।
मैथ्यू एम।

1
@JonathonReinhart सुझाव: आप अपने प्रश्न में "सख्त अलियासिंग नियम" का उल्लेख कर सकते हैं ताकि चर्चा को और अधिक महत्वपूर्ण पहलुओं की ओर ले जा सके।
rwong

3

यहां ऐसी स्थिति है जहां अपारदर्शी हैंडल की आवश्यकता है;

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

जब लाइब्रेरी में दो या दो से अधिक प्रकार के प्रकार होते हैं, जिसमें फ़ील्ड के एक ही हेडर भाग होते हैं, जैसे ऊपर "प्रकार", तो इन संरचना प्रकारों को एक सामान्य मूल संरचना (जैसे C ++ में बेस क्लास) माना जा सकता है।

आप हेडर भाग को "स्ट्रक्चर इंजन" के रूप में परिभाषित कर सकते हैं, जैसे;

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) { ... }

लेकिन यह एक वैकल्पिक निर्णय है क्योंकि स्ट्रक्चर इंजन के उपयोग की परवाह किए बिना टाइप-कास्ट की आवश्यकता होती है।

निष्कर्ष

कुछ मामलों में, ऐसे कारण हैं कि अपारदर्शी नाम के बजाय अपारदर्शी हैंडल का उपयोग किया जाता है।


मुझे लगता है कि एक संघ का उपयोग खतरनाक जातियों के बजाय इस क्षेत्र को सुरक्षित बनाता है जो स्थानांतरित हो सकते हैं। इस जिस्ट की जाँच करें मैंने एक पूर्ण उदाहरण दिखाते हुए एक साथ रखा।
जोनाथन रेनहार्ट

लेकिन वास्तव में, switch"वर्चुअल फ़ंक्शंस" का उपयोग करके पहली जगह से बचना शायद आदर्श है, और पूरे मुद्दे को हल करता है।
जोनाथन रेनहार्ट

मेरा सुझाव दिया गया है की तुलना में जिस्ट में आपका डिजाइन अधिक जटिल है। निश्चित रूप से, यह कास्टिंग को कम, प्रकार-सुरक्षित और स्मार्ट बनाता है, लेकिन अधिक कोड और प्रकार का परिचय देता है। मेरी राय में, यह टाइप-सेफ पाने के लिए बहुत मुश्किल हो गया है। मैं, और शायद पुस्तकालय लेखक का पालन करने का फैसला KISS प्रकार-सुरक्षा के बजाय।
अकीओ ताकाहाशी

यदि आप इसे वास्तव में सरल रखना चाहते हैं, तो आप त्रुटि-जाँच को पूरी तरह से छोड़ सकते हैं!
जोनाथन रेनहार्ट

मेरी राय में, त्रुटि-जांच की कुछ मात्रा की तुलना में डिजाइन सादगी को प्राथमिकता दी जाती है। इस स्थिति में, केवल API फ़ंक्शन में ऐसी त्रुटि-जाँच मौजूद है। इसके अलावा, आप यूनियन का उपयोग करके टाइप-कास्ट को हटा सकते हैं, लेकिन याद रखें कि यूनियन स्वाभाविक रूप से टाइप-असुरक्षित है।
अकीओ ताकाहाशी

2

हैंडल दृष्टिकोण का सबसे स्पष्ट लाभ यह है कि आप बाहरी एपीआई को तोड़े बिना आंतरिक संरचनाओं को संशोधित कर सकते हैं। दी, आपको अभी भी क्लाइंट सॉफ़्टवेयर को संशोधित करना है, लेकिन कम से कम आप इंटरफ़ेस नहीं बदल रहे हैं।

दूसरी बात यह है कि हर एक के लिए एक स्पष्ट एपीआई इंटरफ़ेस प्रदान किए बिना, रनटाइम पर कई अलग-अलग प्रकार से चुनने की क्षमता प्रदान की जाती है। कुछ एप्लिकेशन, जैसे कई अलग-अलग सेंसर प्रकार से सेंसर रीडिंग जहां प्रत्येक सेंसर थोड़ा अलग होता है और थोड़ा अलग डेटा उत्पन्न करता है, इस दृष्टिकोण के लिए अच्छी तरह से प्रतिक्रिया करता है।

चूँकि आप अपने ग्राहकों को किसी भी तरह से संरचना प्रदान कर रहे होंगे, आप बहुत ही सरल API के लिए थोड़े प्रकार की सुरक्षा (जिसे अभी भी रनटाइम पर जांचा जा सकता है) का त्याग करते हैं, इसके लिए कास्टिंग की आवश्यकता होती है।


5
"आप बिना आंतरिक संरचनाओं को संशोधित कर सकते हैं .." - आप आगे की घोषणा दृष्टिकोण के साथ भी कर सकते हैं।
user253751

क्या "फॉरवर्ड डिक्लेरेशन" दृष्टिकोण अभी भी आपको टाइप सिग्नेचर घोषित करने की आवश्यकता नहीं है? और अगर आप स्ट्रक्चर्स बदलते हैं तो क्या उन प्रकार के हस्ताक्षर अभी भी नहीं बदलते हैं?
रॉबर्ट हार्वे

फॉरवर्ड डिक्लेरेशन केवल आपको टाइप का नाम घोषित करने की आवश्यकता है - यह संरचना छिपी हुई है।
इदं आर्य

तब आगे की घोषणा का क्या लाभ होगा यदि यह भी प्रकार संरचना को लागू नहीं करता है?
रॉबर्ट हार्वे

6
@RobertHarvey याद रखें - यह वह सी है जिसके बारे में हम बात कर रहे हैं। कोई विधि नहीं है, इसलिए नाम और संरचना के अलावा अन्य प्रकार के लिए कुछ और नहीं है। यदि यह किया संरचना को लागू यह नियमित रूप से घोषणा करने के लिए समान होना हो गया होता। संरचना को लागू किए बिना नाम को उजागर करने का मुद्दा यह है कि आप फ़ंक्शन हस्ताक्षर में उस प्रकार का उपयोग कर सकते हैं। बेशक, संरचना के बिना आप केवल टाइपर्स के लिए पॉइंटर्स का उपयोग कर सकते हैं, क्योंकि कंपाइलर को यह पता नहीं चल सकता है, लेकिन चूंकि सी का कोई अंतर्निहित सूचक कास्टिंग नहीं है, पॉइंटर्स का उपयोग करना आपकी रक्षा के लिए स्टेटिक-टाइपिंग के लिए पर्याप्त है।
इदं आर्य

2

डेजा वू

नाम, अपारदर्शी संरचना की तुलना में एक अपारदर्शी संभाल कैसे बेहतर है?

मुझे ठीक उसी परिदृश्य का सामना करना पड़ा , केवल कुछ सूक्ष्म अंतरों के साथ। हमारे पास, हमारे एसडीके में, इस तरह की बहुत सी चीजें हैं:

typedef void* SomeHandle;

मेरा मात्र प्रस्ताव यह था कि हम इसे अपने आंतरिक प्रकारों से मिलाएँ:

typedef struct SomeVertex* SomeHandle;

एसडीके का उपयोग करने वाले तीसरे पक्ष को, इसे कोई फर्क नहीं पड़ना चाहिए। यह एक अपारदर्शी प्रकार है। किसे पड़ी है? इसका ABI * या स्रोत संगतता पर कोई प्रभाव नहीं पड़ता है, और एसडीके के नए रिलीज का उपयोग करने के लिए प्लगइन को वैसे भी फिर से चालू करने की आवश्यकता होती है।

* ध्यान दें, जैसा कि gnasher इंगित करता है, वास्तव में ऐसे मामले हो सकते हैं जहां पॉइंटर से लेकर संरचना और शून्य जैसी किसी चीज़ का आकार वास्तव में एक अलग आकार हो सकता है जिस स्थिति में यह ABI को प्रभावित करेगा। उसकी तरह, मैंने कभी भी इसका सामना नहीं किया। लेकिन उस दृष्टिकोण से, दूसरा वास्तव में कुछ अस्पष्ट संदर्भ में पोर्टेबिलिटी में सुधार कर सकता है, इसलिए यह दूसरा पक्ष देने का एक और कारण है, यद्यपि अधिकांश लोगों के लिए संभवतः लूट है।

थर्ड पार्टी बग्स

इसके अलावा, आंतरिक विकास / डीबगिंग के लिए मेरे पास टाइप सुरक्षा से भी अधिक कारण थे। हमारे पास पहले से ही बहुत सारे प्लगइन डेवलपर्स थे जिनके कोड में बग थे क्योंकि दो समान हैंडल ( Panelऔर PanelNew, यानी) दोनों ने void*अपने हैंडल के लिए एक टाइपिडिफ का इस्तेमाल किया था , और वे गलत तरीके से गलत स्थानों पर गलत तरीके से गुजर रहे थे, जिसके परिणामस्वरूप सिर्फ उपयोग कर रहे थे void*प्रत्येक वस्तु के लिए। तो यह वास्तव में उपयोग करने वालों की तरफ कीड़े पैदा कर रहा थाएसडीके। उनके बगों में आंतरिक विकास टीम की भी भारी लागत होती है, क्योंकि वे बग रिपोर्ट के बारे में शिकायतें हमारे एसडीके में भेजते हैं, और हमें प्लगइन को डीबग करना होगा और यह पता लगाना होगा कि यह वास्तव में गलत हैंडल से गुजरने वाले प्लगइन में बग के कारण हुआ था गलत स्थानों पर (जिसे आसानी से अनुमति के बिना भी चेतावनी दी जा सकती है, जब हर हैंडल void*या के लिए एक उपनाम है size_t)। इसलिए हम अनावश्यक रूप से तीसरे पक्षों के लिए एक डिबगिंग सेवा प्रदान करने में अपना समय बर्बाद कर रहे थे क्योंकि सभी आंतरिक जानकारी, यहां तक ​​कि हमारे आंतरिक नाम मात्र को छिपाने में वैचारिक पवित्रता की हमारी इच्छा के कारण हुई गलतियों के कारण structs

टाइप्डिफ़ रखते हुए

अंतर यह है कि मैं प्रस्ताव कर रहा था कि हम typedefअभी भी छड़ी करते हैं , न कि ग्राहकों को लिखने के लिए struct SomeVertexजो भविष्य के प्लगइन रिलीज के लिए स्रोत संगतता को प्रभावित करेगा। जबकि मैं व्यक्तिगत रूप structसे एक एसडीके के नजरिए से सी में टाइप-ऑफिंग नहीं करने का विचार पसंद करता हूं , typedefपूरे बिंदु अस्पष्टता के बाद से मदद कर सकता है। इसलिए मेरा सुझाव है कि सार्वजनिक रूप से उजागर एपीआई के लिए इस मानक को शिथिल किया जाए। एसडीके का उपयोग करने वाले ग्राहकों के लिए, इससे कोई फर्क नहीं पड़ता कि एक हैंडल एक संरचना, पूर्णांक, आदि के लिए एक सूचक है या नहीं, केवल एक चीज जो उनके लिए मायने रखती है वह यह है कि दो अलग-अलग हैंडल एक ही डेटा प्रकार को उपनाम नहीं करते हैं ताकि वे न हों गलत स्थान पर गलत तरीके से गलत तरीके से गुजरना।

जानकारी टाइप करें

जहां कास्टिंग से बचना सबसे ज्यादा मायने रखता है, वहीं आंतरिक देवता। एसडीके से सभी आंतरिक नामों को छिपाने का इस तरह का सौंदर्य कुछ वैचारिक सौंदर्य है जो सभी प्रकार की जानकारी खोने की महत्वपूर्ण लागत पर आता है, और हमें महत्वपूर्ण जानकारी प्राप्त करने के लिए हमारे डिबगर्स में अनावश्यक रूप से छिड़कने की आवश्यकता होती है। जबकि एक सी प्रोग्रामर को मोटे तौर पर सी में इसका आदी होना चाहिए, इस आवश्यकता को पूरा करने के लिए बस परेशानी के लिए पूछ रहा है।

वैचारिक विचार

सामान्य तौर पर आप उन प्रकार के डेवलपर्स के लिए देखना चाहते हैं, जो सभी व्यावहारिक, दैनिक आवश्यकताओं के ऊपर शुद्धता का कुछ वैचारिक विचार रखते हैं। वे कुछ यूटोपियन आदर्श की तलाश में अपने कोडबेस की स्थिरता को जमीन पर चलाएंगे, जिससे पूरी टीम डर से बाहर रेगिस्तान में सनटैन लोशन से बचती है, यह अप्राकृतिक है और इसमें विटामिन डी की कमी हो सकती है, जबकि आधे चालक दल त्वचा कैंसर से मर रहे हैं।

उपयोगकर्ता-अंत पसंद

यहां तक ​​कि एपीआई का उपयोग करने वालों के सख्त उपयोगकर्ता-अंत के दृष्टिकोण से, क्या वे छोटी गाड़ी एपीआई या एक एपीआई पसंद करते हैं जो अच्छी तरह से काम करता है, लेकिन कुछ नाम को उजागर करता है जो वे विनिमय में शायद ही ध्यान रख सकते हैं? क्योंकि यह व्यावहारिक व्यापार बंद है। सामान्य संदर्भ के बाहर अनावश्यक रूप से जानकारी खोने से बग का जोखिम बढ़ रहा है, और कई वर्षों से टीम-वाइड सेटिंग में बड़े पैमाने पर कोडबेस से, मर्फी का कानून काफी हद तक लागू होता है। यदि आप अत्यधिक रूप से बगों का जोखिम बढ़ाते हैं, तो संभावना है कि आप कम से कम कुछ और कीड़े प्राप्त कर लेंगे। एक बड़ी टीम की स्थापना में बहुत लंबा समय नहीं लगता है कि कल्पना करने योग्य हर तरह की मानवीय गलती अंततः एक वास्तविकता में एक संभावित से चली जाएगी।

इसलिए शायद यह एक सवाल है कि उपयोगकर्ताओं के लिए। "क्या आप एक बर्गियर एसडीके या एक को पसंद करेंगे जो कुछ आंतरिक अपारदर्शी नामों को उजागर करता है जिनके बारे में आपने कभी ध्यान नहीं दिया होगा?" और अगर यह प्रश्न गलत द्वंद्वात्मकता को सामने रखता है, तो मैं कहूंगा कि बहुत बड़े पैमाने पर सेटिंग में अधिक टीम चौड़ा अनुभव इस तथ्य की सराहना करने के लिए आवश्यक है कि बग के लिए एक उच्च जोखिम अंततः दीर्घकालिक में वास्तविक कीड़े प्रकट करेगा। यह बहुत कम मायने रखता है कि डेवलपर बग से बचने के लिए कितना आश्वस्त है। एक टीम-वाइड सेटिंग में, यह सबसे कमजोर लिंक के बारे में सोचने में अधिक मदद करता है, और कम से कम सबसे आसान और त्वरित तरीके से उन्हें ट्रिपिंग से रोकने के लिए।

प्रस्ताव

इसलिए मैं यहां एक समझौता करने का सुझाव दूंगा जो आपको अभी भी सभी डिबगिंग लाभों को बनाए रखने की क्षमता देगा:

typedef struct engine* enh;

... यहां तक ​​कि टाइपराइफ़िंग की कीमत पर struct, क्या यह वास्तव में हमें मार डालेगा? शायद नहीं, इसलिए मैं आपके हिस्से पर भी कुछ व्यावहारिकता की सिफारिश करता हूं, लेकिन अधिक से अधिक डेवलपर उस व्यक्ति के लिए जो size_tयहां उपयोग करके डिबगिंग को कठिन बनाना पसंद करेंगे और आगे की जानकारी छुपाने के लिए बिना किसी अच्छे कारण के पूर्णांक से / के लिए कास्टिंग करना जो पहले से ही 99 है। % उपयोगकर्ता के लिए छिपा हुआ है और संभवतः इससे अधिक नुकसान नहीं कर सकता है size_t


1
यह एक छोटा अंतर है: सी स्टैंडर्ड के अनुसार, सभी "पॉइंटर टू स्ट्रक्चर" का समान प्रतिनिधित्व है, इसलिए सभी "पॉइंटर टू यूनियन" करें, इसलिए "शून्य *" और "चार *" करें, लेकिन एक शून्य * और एक "पॉइंटर" संरचना के लिए "अलग आकार () और / या अलग प्रतिनिधित्व कर सकते हैं। व्यवहार में, मैंने यह कभी नहीं देखा।
gnasher729

@ gnasher729 वही, शायद मुझे कास्टिंग में पोर्टेबिलिटी के संभावित नुकसान के संबंध में उस हिस्से को अर्हता प्राप्त करनी चाहिए void*या size_tसुपरफ्लुएंट कास्टिंग से बचने के लिए एक और कारण के रूप में वापस करना चाहिए। मैं थोड़े इसे छोड़ दिया जब से मैं इसे कभी नहीं देखा अभ्यास में दिया प्लेटफार्मों हम (जो हमेशा डेस्कटॉप प्लेटफार्मों थे: linux, OSX, Windows)।

1
हम साथ समाप्त हो गयाtypedef struct uc_struct uc_engine;
जोनाथन रीनहार्ट

1

मुझे संदेह है कि असली कारण जड़ता है, यही उन्होंने हमेशा किया है और यह काम करता है इसलिए इसे क्यों बदलें?

मुख्य कारण जो मैं देख सकता हूं वह यह है कि अपारदर्शी हैंडल डिजाइनर को इसके पीछे कुछ भी डाल देता है, न कि केवल एक संरचना। यदि एपीआई लौटता है और कई अपारदर्शी प्रकारों को स्वीकार करता है, तो वे सभी कॉलर को समान दिखते हैं और फाइन प्रिंट में बदलाव होने पर कभी भी कोई संकलन समस्याएं या पुनरावृत्ति की आवश्यकता नहीं होती है। यदि en_NewFlidgetTwiddler (हैंडल ** newTwiddler) एक हैंडल के बजाय एक संकेतक को Twiddler में वापस करने के लिए बदलता है, तो API परिवर्तित नहीं होता है और कोई भी नया कोड चुपचाप एक पॉइंटर का उपयोग कर रहा होगा, जहां वह हैंडल का उपयोग कर रहा था। साथ ही, ओएस का कोई खतरा नहीं है या कुछ और चुपचाप पॉइंटर को "फिक्सिंग" करता है अगर यह सीमाओं के पार हो जाता है।

निस्संदेह, इसका नुकसान यह है कि कॉल करने वाला इसमें कुछ भी खिला सकता है। आपके पास 64 बिट है? एपीआई कॉल में 64 बिट स्लॉट में इसे देखें और देखें कि क्या होता है।

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

दोनों संकलन करते हैं, लेकिन मैं शर्त लगाता हूं कि उनमें से केवल एक ही वह करता है जो आप चाहते हैं।


1

मेरा मानना ​​है कि सी लाइब्रेरी एपीआई को शुरुआती लोगों द्वारा दुर्व्यवहार से बचाने के लिए दृष्टिकोण लंबे समय से दर्शन से उपजा है।

विशेष रूप से,

  • लाइब्रेरी लेखकों को पता है कि यह संरचना का एक संकेतक है, और संरचना का विवरण पुस्तकालय कोड को दिखाई देता है।
  • पुस्तकालय का उपयोग करने वाले सभी अनुभवी प्रोग्रामर भी जानते हैं कि यह कुछ अपारदर्शी संरचनाओं का सूचक है;
    • उनके पास उन संरचनाओं में संग्रहीत बाइट्स के साथ गड़बड़ न करने के लिए जानने के लिए पर्याप्त कठिन दर्दनाक अनुभव था ।
  • अनुभवहीन प्रोग्रामर न तो जानते हैं।
    • वे memcpyअपारदर्शी डेटा की कोशिश करेंगे या संरचना के अंदर बाइट्स या शब्दों को बढ़ाएँगे। हैकिंग पर जाओ।

लंबे समय तक पारंपरिक प्रतिवाद है:

  • इस तथ्य को स्पष्ट करें कि एक अपारदर्शी हैंडल वास्तव में एक अपारदर्शी संरचना का सूचक है जो एक ही प्रक्रिया-मेमोरी-स्पेस में मौजूद है।
    • यह दावा करने के लिए कि यह एक पूर्णांक मान है जिसमें बिट्स की संख्या समान है void*
    • एक्स्ट्रा-सर्कमस्पेक्ट होने के लिए, पॉइंटर के बिट्स को भी, जैसे
      struct engine* peng = (struct engine*)((size_t)enh ^ enh_magic_number);

यह सिर्फ यह कहना है कि इसकी लंबी परंपराएं हैं; मेरी कोई व्यक्तिगत राय नहीं थी कि यह सही है या गलत।


3
हास्यास्पद एक्सोर को छोड़कर, मेरा समाधान उस सुरक्षा को भी प्रदान करता है। क्लाइंट संरचना के आकार या सामग्री से अनजान रहता है, प्रकार सुरक्षा के अतिरिक्त लाभ के साथ। मैं यह नहीं देखता कि पॉइंटर को रखने के लिए size_t का दुरुपयोग करना कितना बेहतर है।
जोनाथन रेइनहर्ट

@JonathonReinhart यह बहुत संभावना नहीं है कि ग्राहक वास्तव में संरचना से अनजान होंगे। प्रश्न अधिक है: क्या वे संरचना प्राप्त कर सकते हैं और क्या वे आपके पुस्तकालय में एक संशोधित संस्करण लौटा सकते हैं। न केवल खुले स्रोत के साथ, बल्कि आम तौर पर। इसका समाधान आधुनिक मेमोरी विभाजन है, न कि मूर्खतापूर्ण XOR।
Móż

तुम्हारी किस बारे में बोलने की इच्छा थी? मैं केवल इतना कह रहा हूं कि आप किसी भी कोड को संकलित नहीं कर सकते हैं जो उक्त संरचना के लिए एक संकेतक को निष्क्रिय करने का प्रयास करता है, या ऐसा कुछ भी करता है जिसे इसके आकार के ज्ञान की आवश्यकता होती है। यदि आप वास्तव में करना चाहते हैं, तो पूरी प्रक्रिया के दौरान आप (, 0,) को याद कर सकते हैं।
जोनाथन रेइनहर्ट

6
यह तर्क मैकियावेली के खिलाफ रखवाली जैसा लगता है । यदि उपयोगकर्ता मेरे एपीआई में कचरा पास करना चाहता है, तो कोई रास्ता नहीं है कि मैं उन्हें रोक सकता हूं। इस तरह का एक असुरक्षित इंटरफ़ेस पेश करना मुश्किल से ही उसकी मदद करता है, क्योंकि यह वास्तव में एपीआई के आकस्मिक दुरुपयोग को आसान बनाता है।
कॉमिकसंस

@ComicSansMS, "आकस्मिक" उल्लेख के लिए धन्यवाद के रूप में है कि क्या मैं हूँ वास्तव में यहाँ रोकने के लिए कोशिश कर रहा।
जोनाथन रेइनहर्ट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.