क्यों यह दावा किया गया है कि डेरीफ्रेंसिंग टाइप-पाइंट पॉइंटर वार्निंग कंपाइलर-विशिष्ट है?


38

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

मेरा प्रश्न नीचे दिए गए कोड के लिए विशिष्ट है: सूचक के पते को void**इस चेतावनी के लिए अर्हता प्राप्त करने के लिए क्यों चुना जाता है (त्रुटि के माध्यम से प्रचारित -Werror)?

इसके अलावा, इस कोड को कई लक्ष्य आर्किटेक्चर के लिए संकलित किया गया है, जिसमें से केवल एक ही चेतावनी / त्रुटि उत्पन्न करता है - इसका मतलब यह हो सकता है कि यह वैध रूप से संकलक संस्करण-विशिष्ट कमी है?

// main.c
#include <stdlib.h>

typedef struct Foo
{
  int i;
} Foo;

void freeFunc( void** obj )
{
  if ( obj && * obj )
  {
    free( *obj );
    *obj = NULL;
  }
}

int main( int argc, char* argv[] )
{
  Foo* f = calloc( 1, sizeof( Foo ) );
  freeFunc( (void**)(&f) );

  return 0;
}

यदि मेरी समझ, ऊपर कहा गया है, सही है, एक void**, अभी भी सिर्फ एक सूचक होने के नाते, यह सुरक्षित कास्टिंग होना चाहिए।

क्या इस संकलक-विशिष्ट चेतावनी / त्रुटि को शांत करने वाले अंतराल का उपयोग नहीं करने के लिए एक समाधान है ? यानी मैं समझता हूं कि यह क्यों और इस मुद्दे को हल करेगा, लेकिन मैं इस दृष्टिकोण से बचना चाहूंगा क्योंकि मैं freeFunc() NULL आईएनजी का लाभ लेना चाहता हूं ताकि एक आउट-आर्ग हो सके:

void* tmp = f;
freeFunc( &tmp );
f = NULL;

समस्या संकलक (एक में से एक):

user@8d63f499ed92:/build$ /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc --version && /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc -Wall -O2 -Werror ./main.c
i686-fc3-linux-gnu-gcc (GCC) 3.4.5
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

./main.c: In function `main':
./main.c:21: warning: dereferencing type-punned pointer will break strict-aliasing rules

user@8d63f499ed92:/build$

शिकायतकर्ता संकलक (कई में से एक):

user@8d63f499ed92:/build$ /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc --version && /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc -Wall -O2 -Werror ./main.c
i686-rh73-linux-gnu-gcc (GCC) 3.2.3
Copyright (C) 2002 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

user@8d63f499ed92:/build$

अपडेट: मैंने आगे पाया है कि चेतावनी तब उत्पन्न होती है जब विशेष रूप से संकलित किया जाता है -O2(अभी भी केवल "समस्या संकलक" के साथ)



1
"ए void**, अभी भी एक संकेतक होने के नाते, यह सुरक्षित कास्टिंग होना चाहिए।" वाह वहाँ कंजूसी! लगता है जैसे आपके पास कुछ मौलिक धारणाएं हैं। बाइट्स और लीवर के संदर्भ में कम और अमूर्तता के संदर्भ में अधिक सोचने की कोशिश करें, क्योंकि यही आप वास्तव में प्रोग्रामिंग कर रहे हैं
लाइटवेट रेस इन ऑर्बिट

7
Tangentially, आपके द्वारा उपयोग किए जा रहे संकलन 15 और 17 साल पुराने हैं! मैं किसी एक पर निर्भर नहीं होता।
तवियन बार्न्स

4
@TavianBarnes इसके अलावा, यदि आपको किसी भी कारण से GCC 3 पर भरोसा करना चाहिए, तो एंड-ऑफ़-द-एंड संस्करण का उपयोग करना सबसे अच्छा है, जो कि 3.4.6 था, मुझे लगता है। आराम करने के लिए रखी जाने से पहले उस श्रृंखला के सभी उपलब्ध सुधारों का लाभ क्यों नहीं उठाया गया।
कज़

C ++ कोडिंग मानक किस तरह के उन सभी स्थानों को निर्धारित करता है?
पीटर मोर्टेंसन

जवाबों:


33

प्रकार void**का एक मान प्रकार की एक वस्तु के लिए एक संकेतक है void*। एक प्रकार Foo*की वस्तु प्रकार की वस्तु नहीं है void*

प्रकार के मूल्यों के बीच एक अंतर्निहित रूपांतरण है Foo*और void*। यह रूपांतरण मूल्य के प्रतिनिधित्व को बदल सकता है। इसी तरह, आप लिख सकते हैं int n = 3; double x = n;और इसमें xमूल्य के लिए सेटिंग का अच्छी तरह से परिभाषित व्यवहार है 3.0, लेकिन double *p = (double*)&n;अपरिभाषित व्यवहार है (और व्यवहार में किसी भी सामान्य वास्तुकला पर p"सूचक" के लिए सेट नहीं होगा 3.0)।

आर्किटेक्चर जहां विभिन्न प्रकार के पॉइंटर्स टू ऑब्जेक्ट्स के अलग-अलग प्रतिनिधित्व होते हैं वे आजकल दुर्लभ हैं, लेकिन उन्हें सी मानक द्वारा अनुमति दी जाती है। शब्द संकेत के साथ (दुर्लभ) पुरानी मशीनें हैं जो स्मृति और बाइट पॉइंटर्स में एक शब्द के पते हैं जो इस शब्द में बाइट ऑफसेट के साथ एक शब्द के पते हैं; Foo*एक शब्द सूचक void*होगा और इस तरह के आर्किटेक्चर पर एक बाइट सूचक होगा। वसा बिंदुओं के साथ (दुर्लभ) मशीनें हैं जिनमें न केवल ऑब्जेक्ट के पते के बारे में जानकारी होती है, बल्कि इसके प्रकार, इसके आकार और इसकी पहुंच नियंत्रण सूचियों के बारे में भी जानकारी होती है; एक निश्चित प्रकार के लिए एक पॉइंटर में एक अलग प्रतिनिधित्व हो सकता है void*जिसमें रनटाइम पर अतिरिक्त प्रकार की जानकारी की आवश्यकता होती है।

ऐसी मशीनें दुर्लभ हैं, लेकिन सी मानक द्वारा अनुमत हैं। और कुछ सी कंपाइलर कोड को ऑप्टिमाइज़ करने के लिए टाइप-पाइंट पॉइंटर्स को अलग-अलग मानने की अनुमति का लाभ उठाते हैं। कोड का अनुकूलन करने के लिए संकलक की क्षमता के लिए पॉइंटर्स अलियासिंग का जोखिम एक प्रमुख सीमा है, इसलिए संकलक ऐसी अनुमतियों का लाभ उठाते हैं।

एक संकलक आपको यह बताने के लिए स्वतंत्र है कि आप कुछ गलत कर रहे हैं, या चुपचाप वह करें जो आप नहीं चाहते थे, या चुपचाप वह करें जो आप चाहते थे। अपरिभाषित व्यवहार इनमें से किसी को भी अनुमति देता है।

आप freefuncएक मैक्रो बना सकते हैं :

#define FREE_SINGLE_REFERENCE(p) (free(p), (p) = NULL)

यह मैक्रोज़ की सामान्य सीमाओं के साथ आता है: प्रकार की सुरक्षा की कमी, pदो बार मूल्यांकन किया जाता है। ध्यान दें कि यह आपको केवल झूलने वाले बिंदुओं को न छोड़ने की सुरक्षा देता है, अगर pमुक्त ऑब्जेक्ट के लिए एकल सूचक था।


1
और यह जानना अच्छा है कि भले ही Foo*और void*आपकी वास्तुकला पर समान प्रतिनिधित्व हो, लेकिन यह अभी भी उन्हें टाइप करने के लिए अपरिभाषित है।
तवियन बार्न्स

12

A void *को विशेष रूप से C मानक द्वारा भाग में माना जाता है क्योंकि यह एक अपूर्ण प्रकार को संदर्भित करता है। इस इलाज करता है नहीं करने के लिए विस्तार void **के रूप में यह होता है , एक पूरा प्रकार के बिंदु विशेष रूप से void *

सख्त अलियासिंग नियमों का कहना है कि आप एक प्रकार के पॉइंटर को दूसरे प्रकार के पॉइंटर में नहीं बदल सकते हैं और बाद में उस चीज़ को पॉइंटर करते हैं क्योंकि ऐसा करने का अर्थ है एक प्रकार के बाइट्स को दूसरे के रूप में फिर से समझना। एकमात्र अपवाद तब होता है जब एक चरित्र प्रकार में परिवर्तित किया जाता है जो आपको किसी वस्तु के प्रतिनिधित्व को पढ़ने की अनुमति देता है।

आप किसी फ़ंक्शन के बजाय फ़ंक्शन जैसी मैक्रो का उपयोग करके इस सीमा के आसपास प्राप्त कर सकते हैं:

#define freeFunc(obj) (free(obj), (obj) = NULL)

जिसे आप इस तरह से कॉल कर सकते हैं:

freeFunc(f);

हालांकि इसकी एक सीमा है, क्योंकि उपरोक्त मैक्रो मूल्यांकन करेगा obj दो बार । यदि आप GCC का उपयोग कर रहे हैं, तो इसे कुछ एक्सटेंशन, विशेष रूप से typeofकीवर्ड और स्टेटमेंट एक्सप्रेशन के साथ टाला जा सकता है :

#define freeFunc(obj) ({ typeof (&(obj)) ptr = &(obj); free(*ptr); *ptr = NULL; })

3
इच्छित व्यवहार का बेहतर कार्यान्वयन प्रदान करने के लिए +1। एकमात्र समस्या जो मैं देख रहा हूं #define, वह यह है कि यह objदो बार मूल्यांकन करेगा । मैं उस दूसरे मूल्यांकन से बचने का एक अच्छा तरीका नहीं जानता, हालांकि। यहां तक ​​कि एक स्टेटमेंट एक्सप्रेशन (GNU एक्सटेंशन) भी उस ट्रिक को नहीं करेगा जैसा कि आपको objइसके मूल्य का उपयोग करने के बाद असाइन करने की आवश्यकता है ।
विस्फ़ोटक -

2
@cmaster: यदि आप कथन अभिव्यक्ति जैसे GNU एक्सटेंशन का उपयोग करने के इच्छुक हैं, तो आप दो बार typeofमूल्यांकन से बचने के लिए उपयोग कर सकते हैं :। obj#define freeFunc(obj) ({ typeof(&(obj)) ptr = &(obj); free(*ptr); *ptr = NULL; })
बर्बाद करें

@ruakh बहुत मस्त :-) अगर जवाब में dbush इसे संपादित करेगा तो बहुत अच्छा होगा, इसलिए यह टिप्पणियों के साथ बड़े पैमाने पर नष्ट नहीं होगा।
विस्फ़ोटक -

9

एक प्रकार का चेतावनी सूचक इंगित करना UB है और आप इस पर भरोसा नहीं कर सकते कि क्या होगा।

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

एक ऐसा मामला जो आपको यह समझने में मदद कर सकता है कि इस मामले में टाइपिंग का तरीका खराब क्यों हो सकता है, यह है कि आपका कार्य किसी आर्किटेक्चर पर काम नहीं करेगा sizeof(Foo*) != sizeof(void*)। यह मानक द्वारा अधिकृत है, हालांकि मुझे ऐसा कोई वर्तमान नहीं पता है जिसके लिए यह सत्य है।

एक फ़ंक्शन के बजाय मैक्रो का उपयोग करने के लिए वर्कअराउंड होगा।

ध्यान दें कि freeअशक्त बिंदुओं को स्वीकार करता है।


2
आकर्षक कि यह संभव है कि sizeof Foo* != sizeof void*। मैंने कभी भी "वाइल्ड" पॉइंटर साइज़ में टाइप-डिपेंडेंट होने का सामना नहीं किया है, इसलिए पिछले कुछ वर्षों में, मैं इस बात पर विचार करने के लिए आया हूं कि एक्साइओमैटिक है कि पॉइंटर साइज़ किसी दिए गए आर्किटेक्चर पर समान हैं।
स्टोनथ्रो

1
@Stonethrow मानक उदाहरण वसा सूचक शब्द पता वास्तुकला में बाइट्स को संबोधित करने के लिए उपयोग किया जाता है। लेकिन मुझे लगता है कि वर्तमान शब्द पता करने योग्य मशीनें वैकल्पिक आकार का वर्ण == आकार शब्द का उपयोग करती हैं
एपीग्रामग्राम

2
ध्यान दें कि आकार को आकार के लिए छोटा किया जाना है ...
एंटि हापाला

@StoneThrow: सूचक आकार के बावजूद, टाइप-आधारित उर्फ ​​विश्लेषण इसे असुरक्षित बनाता है; इस compilers यह सोचते हैं कि एक के माध्यम से एक दुकान से अनुकूलित करने में सहायता float*एक बदलाव नहीं करेगी int32_tवस्तु है, तो जैसे एक int32_t*की जरूरत नहीं है int32_t *restrict ptrयह एक ही स्मृति की ओर संकेत नहीं है संभालने के लिए संकलक के लिए। void**एक Foo*वस्तु को संशोधित करने के लिए नहीं माना जा रहा है के माध्यम से दुकानों के लिए एक ही ।
पीटर कॉर्डेस

4

यह कोड C मानक के अनुसार अमान्य है, इसलिए यह कुछ मामलों में काम कर सकता है, लेकिन जरूरी नहीं कि यह पोर्टेबल हो।

एक अलग पॉइंटर प्रकार के लिए डाले गए पॉइंटर के माध्यम से एक मूल्य तक पहुंचने के लिए "सख्त अलियासिंग नियम" 6.5 7 में पाया गया है:

एक वस्तु का अपना संग्रहीत मूल्य केवल एक लैवल्यू अभिव्यक्ति द्वारा एक्सेस किया जाना चाहिए, जिसमें निम्न प्रकार हैं:

  • वस्तु के प्रभावी प्रकार के साथ संगत एक प्रकार,

  • वस्तु के प्रभावी प्रकार के साथ संगत एक प्रकार का एक योग्य संस्करण,

  • एक प्रकार जो वस्तु के प्रभावी प्रकार के अनुरूप हस्ताक्षरित या अहस्ताक्षरित प्रकार है,

  • एक प्रकार जो वस्तु के प्रभावी प्रकार के एक योग्य संस्करण के अनुरूप हस्ताक्षरित या अहस्ताक्षरित प्रकार है,

  • एक समग्र या संघ प्रकार जिसमें इसके सदस्यों में से एक प्रकार शामिल है (सहित, पुनरावर्ती, उपसमूह या निहित संघ का सदस्य), या

  • एक चरित्र प्रकार।

आपके *obj = NULL;कथन में, ऑब्जेक्ट में प्रभावी प्रकार है, Foo*लेकिन *objप्रकार के साथ अंतराल अभिव्यक्ति द्वारा पहुँचा जाता है void*

6.7.5.1 पैरा 2 में, हमारे पास है

दो पॉइंटर प्रकार संगत होने के लिए, दोनों पहचान योग्य होंगे और दोनों ही संगत प्रकार के संकेत होंगे।

तो void*औरFoo* क्वालिफायर के साथ संगत प्रकार या संगत प्रकार नहीं जोड़े गए हैं, और निश्चित रूप से सख्त अलियासिंग नियम के अन्य विकल्पों में से कोई भी फिट नहीं है।

यद्यपि तकनीकी कारण कोड अमान्य नहीं है, यह भी खंड 6.2.5 पैरा 26 नोट करने के लिए प्रासंगिक है:

एक पॉइंटर को voidएक चरित्र प्रकार के पॉइंटर के रूप में समान प्रतिनिधित्व और संरेखण आवश्यकताएं होनी चाहिए। इसी प्रकार, संगत प्रकारों के योग्य या अयोग्य संस्करणों के संकेत एक ही प्रतिनिधित्व और संरेखण आवश्यकताओं होंगे। संरचना प्रकार के सभी संकेत एक दूसरे के समान प्रतिनिधित्व और संरेखण आवश्यकताएं होंगी। यूनियन प्रकार के सभी संकेत एक दूसरे के समान प्रतिनिधित्व और संरेखण आवश्यकताओं होंगे। अन्य प्रकार के पॉइंटर्स के लिए समान प्रतिनिधित्व या संरेखण आवश्यकताओं की आवश्यकता नहीं होती है।

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


2

अन्य उत्तरों ने जो कहा है, उसके शीर्ष पर, यह C में एक क्लासिक प्रतिमान है, और जिसे आग से जलाया जाना चाहिए। यह इसमें दिखाई देता है:

  1. फ्री-एंड-नल-आउट फ़ंक्शंस जैसे कि आपको चेतावनी मिली है।
  2. आवंटन फ़ंक्शंस जो लौटने के मानक सी मुहावरे को दूर कर देते हैं void *(जो इस समस्या से ग्रस्त नहीं है क्योंकि इसमें टाइपिंग पाइंट के बजाय एक मूल्य रूपांतरण शामिल है ), बजाय एक त्रुटि ध्वज को लौटाए और एक पॉइंटर-टू-पॉइंटर के माध्यम से परिणाम को संग्रहीत करना।

(1) के एक अन्य उदाहरण के लिए, ffmpeg / libavcodec के av_freeकार्य में एक लंबे समय से कुख्यात मामला था । मेरा मानना ​​है कि यह अंततः एक मैक्रो या किसी अन्य चाल के साथ तय किया गया था, लेकिन मुझे यकीन नहीं है।

(2) के लिए, दोनों cudaMallocऔर posix_memalignउदाहरण हैं।

न तो मामले में इंटरफ़ेस को स्वाभाविक रूप से अवैध उपयोग की आवश्यकता होती है , लेकिन यह दृढ़ता से इसे प्रोत्साहित करता है, और केवल सही उपयोग को स्वीकार करता है प्रकार के अतिरिक्त अस्थायी ऑब्जेक्ट के साथ जो void *फ्री-एंड-नल-आउट कार्यक्षमता के उद्देश्य को पराजित करता है, और आवंटन को अजीब बनाता है।


क्या आपके पास एक लिंक है जो इस बारे में अधिक बता रहा है कि (1) एक प्रतिमान क्यों है? मुझे नहीं लगता कि मैं इस स्थिति / तर्क से परिचित हूं और अधिक सीखना चाहूंगा।
स्टोनथ्रो

1
@StoneThrow: यह वास्तव में सरल है - आशय यह है कि ऑब्जेक्ट को पॉइंटर को स्टोर करने से रोकने के लिए गलत तरीके से मेमोरी को पॉइंटर किया जा रहा है, लेकिन यह वास्तव में ऐसा ही कर सकता है यदि कॉलर वास्तव में पॉइंटर को ऑब्जेक्ट में स्टोर कर रहा है टाइप करें void *और हर बार इसे परिवर्तित करने के लिए कास्टिंग / परिवर्तित करें। यह बहुत संभावना नहीं है। यदि कॉलर किसी अन्य प्रकार के पॉइंटर को स्टोर कर रहा है, तो यूबी को आमंत्रित किए बिना फ़ंक्शन को कॉल करने का एकमात्र तरीका पॉइंटर को टाइप के एक अस्थायी ऑब्जेक्ट पर कॉपी करना है void *और उस के पते को फ्रीिंग फ़ंक्शन को पास करना है, और फिर बस ...
R .. गिटहब स्टॉप हेल्पिंग ICE

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

2

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

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.