स्टैक ऐरे के लिए मैं पॉइंटर से पॉइंटर तक क्यों नहीं पहुंच सकता?


35

कृपया निम्नलिखित कोड देखें। यह char**एक फ़ंक्शन के रूप में एक सरणी पास करने की कोशिश करता है :

#include <stdio.h>
#include <stdlib.h>

static void printchar(char **x)
{
    printf("Test: %c\n", (*x)[0]);
}

int main(int argc, char *argv[])
{
    char test[256];
    char *test2 = malloc(256);

    test[0] = 'B';
    test2[0] = 'A';

    printchar(&test2);            // works
    printchar((char **) &test);   // crashes because *x in printchar() has an invalid pointer

    free(test2);

    return 0;
}

तथ्य यह है कि मैं इसे केवल स्पष्ट रूप &test2से char**पहले ही संकेत देने के लिए कास्टिंग करके संकलन करने के लिए प्राप्त कर सकता हूं कि यह कोड गलत है।

फिर भी, मैं सोच रहा हूं कि इसके बारे में क्या गलत है। मैं एक पॉइंटर को पॉइंटर को डायनेमिक रूप से आवंटित एरे में पास कर सकता हूं लेकिन स्टैक पर एरे के लिए पॉइंटर को पॉइंटर पास नहीं कर सकता। बेशक, मैं आसानी से एक अस्थायी चर के लिए सरणी असाइन करके समस्या के आसपास काम कर सकता हूं, जैसे:

char test[256];
char *tmp = test;
test[0] = 'B';
printchar(&tmp);

फिर भी, कर सकते हैं किसी कारण है कि यह कलाकारों को काम नहीं करता है मुझे समझा char[256]करने के लिए char**सीधे?

जवाबों:


29

क्योंकि testसूचक नहीं है।

&testआपको सरणी के लिए एक संकेतक मिलता है, प्रकार का char (*)[256], जो कि संगत नहीं है char**(क्योंकि सरणी सूचक नहीं है)। इससे अपरिभाषित व्यवहार होता है।


3
लेकिन फिर सी कंपाइलर कुछ टाइप char (*)[256]करने की अनुमति क्यों देता है char**?
ComFreek

@ कॉम मुझे शक है कि अधिकतम चेतावनियों और -वायरर के साथ, यह अनुमति नहीं देता है।
16

@ कॉम: यह वास्तव में इसकी अनुमति नहीं देता है। मुझे संकलक को इसे स्पष्ट रूप से कास्टिंग करके स्वीकार करने के लिए मजबूर करना होगा char**। उस कास्ट के बिना, यह संकलन नहीं करता है।
एंड्रियास

38

testएक सरणी है, एक सूचक नहीं है, और &testसरणी के लिए एक सूचक है। यह एक पॉइंटर को पॉइंटर नहीं है।

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

  • सरणी का संचालन है sizeof
  • सरणी का संचालन है &
  • सरणी एक स्ट्रिंग शाब्दिक है जिसका उपयोग किसी सरणी को शुरू करने के लिए किया जाता है।

में &test, सरणी का ऑपरेंड है &, इसलिए स्वचालित रूपांतरण नहीं होता है। का परिणाम &test256 के एक सरणी के लिए एक संकेतक है char, जिसमें प्रकार है char (*)[256], नहीं char **

करने के लिए एक सूचक के लिए सूचक प्राप्त करने के लिए charसे test, आप पहली बार के लिए एक सूचक बनाने के लिए की आवश्यकता होगी char। उदाहरण के लिए:

char *p = test; // Automatic conversion of test to &test[0] occurs.
printchar(&p);  // Passes a pointer to a pointer to char.

इस बारे में सोचने का एक और तरीका यह है कि यह महसूस किया जाए कि testपूरे ऑब्जेक्ट को नाम दिया गया है - पूरे सरणी 256 char। यह एक पॉइंटर का नाम नहीं देता है, इसलिए, &testऐसा कोई पॉइंटर नहीं है जिसका पता लिया जा सकता है, इसलिए यह उत्पादन नहीं कर सकता है char **। एक बनाने के लिए char **, आपको पहले एक होना चाहिए char *


1
क्या यह तीन अपवादों की सूची संपूर्ण है?
रसलान

8
@ रसेलन: हाँ, प्रति C 2018 6.3.2.1 3.
एरिक पोस्टपिसिल

ओह, और C11 में भी और के _Alignofअलावा उल्लिखित ऑपरेटर था । मुझे आश्चर्य है कि क्यों वे यह ... हटायाsizeof&
रुस्लान

@Ruslan: यह हटा दिया गया था क्योंकि यह एक गलती थी। _Alignofकेवल एक प्रकार को एक ऑपरेंड के रूप में स्वीकार करता है और एक सरणी या किसी अन्य ऑब्जेक्ट को ऑपरेंड के रूप में स्वीकार नहीं करता है। (मुझे नहीं पता क्यों; यह वाक्य-रचना और व्याकरणिक रूप से ऐसा लगता है sizeof, लेकिन ऐसा नहीं है।)
एरिक पोस्टपिसिल

6

का प्रकार test2है char *। तो, &test2वसीयत char **का प्रकार जो पैरामीटर xके प्रकार के साथ संगत है printchar()
का प्रकार testहै char [256]। तो, के प्रकार के &testहो जाएगा char (*)[256]जो है नहीं पैरामीटर के प्रकार के साथ संगत xकी printchar()

मुझे आप के पते के मामले में अंतर दिखाने दें testऔर test2

#include <stdio.h>
#include <stdlib.h>

static void printchar(char **x)
{
    printf("x = %p\n", (void*)x);
    printf("*x  = %p\n", (void*)(*x));
    printf("Test: %c\n", (*x)[0]);
}

int main(int argc, char *argv[])
{
    char test[256];
    char *test2 = malloc(256);

    test[0] = 'B';
    test2[0] = 'A';

    printf ("test2 : %p\n", (void*)test2);
    printf ("&test2 : %p\n", (void*)&test2);
    printf ("&test2[0] : %p\n", (void*)&test2[0]);
    printchar(&test2);            // works

    printf ("\n");
    printf ("test : %p\n", (void*)test);
    printf ("&test : %p\n", (void*)&test);
    printf ("&test[0] : %p\n", (void*)&test[0]);

    // Commenting below statement
    //printchar((char **) &test);   // crashes because *x in printchar() has an invalid pointer

    free(test2);

    return 0;
}

आउटपुट:

$ ./a.out 
test2 : 0x7fe974c02970
&test2 : 0x7ffee82eb9e8
&test2[0] : 0x7fe974c02970
x = 0x7ffee82eb9e8
*x  = 0x7fe974c02970
Test: A

test : 0x7ffee82eba00
&test : 0x7ffee82eba00
&test[0] : 0x7ffee82eba00

यहाँ ध्यान दें:

के उत्पादन (स्मृति पता) test2और &test2[0]है संख्यानुसार एक ही है और उनके प्रकार है जो भी एक ही है char *
लेकिन test2और &test2अलग-अलग पते हैं और उनका प्रकार भी अलग है।
का प्रकार test2है char *
का प्रकार &test2है char **

x = &test2
*x = test2
(*x)[0] = test2[0] 

निर्गम (स्मृति पता) की test, &testऔर &test[0]है संख्यानुसार ही लेकिन उनके प्रकार अलग है
का प्रकार testहै char [256]
का प्रकार &testहै char (*) [256]
का प्रकार &test[0]है char *

जैसा कि आउटपुट से पता चलता &testहै &test[0]

x = &test[0]
*x = test[0]       //first element of test array which is 'B'
(*x)[0] = ('B')[0]   // Not a valid statement

इसलिए आपको विभाजन की गलती मिल रही है।


3

आप पॉइंटर को पॉइंटर तक नहीं पहुंचा सकते क्योंकि पॉइंटर &testनहीं है - यह एक एरे है।

यदि आप किसी सरणी का पता लेते हैं, तो सरणी और सरणी के पते को कास्ट करें (void *), और उनकी तुलना करें, वे (संभावित पॉइंटर पेन्ट्री को रोकना) समकक्ष होंगे।

आप वास्तव में जो कर रहे हैं वह इसी के समान है (फिर से, सख्त अलियासिंग को छोड़कर):

putchar(**(char **)test);

जो काफी स्पष्ट रूप से गलत है।


3

आपका कोड स्मृति को इंगित करने के तर्क xकी अपेक्षा करता printcharहै जिसमें ए शामिल है (char *)

पहली कॉल में, यह उपयोग किए जाने वाले भंडारण की ओर इशारा करता test2है और इस प्रकार वास्तव में एक मान है जो (char *)आवंटित मेमोरी की ओर इशारा करता है।

दूसरी कॉल में, हालांकि, ऐसी कोई जगह नहीं है जहां इस तरह के किसी भी (char *)मूल्य को संग्रहीत किया जा सकता है और इसलिए ऐसी स्मृति को इंगित करना असंभव है। करने के लिए कलाकारों (char **)ने आपको शामिल किया एक संकलन त्रुटि हटा दिया होता (परिवर्तित करने के बारे में (char *)करने के लिए (char **)), लेकिन यह भंडारण को रोकने के लिए एक पतली हवा से बाहर दिखाई नहीं होगा (char *)परीक्षण का पहला अक्षर तक बात करने के लिए प्रारंभ। सी में पॉइंटर कास्टिंग पॉइंटर के वास्तविक मूल्य को नहीं बदलता है।

आप जो चाहते हैं उसे पाने के लिए, आपको इसे स्पष्ट रूप से करना होगा:

char *tempptr = &temp;
printchar(&tempptr);

मुझे लगता है कि आपका उदाहरण कोड के एक बहुत बड़े टुकड़े का आसवन है; एक उदाहरण के रूप में, शायद आप printcharउस (char *)मूल्य को बढ़ाना चाहते हैं जो पारित xमूल्य इंगित करता है ताकि अगले कॉल पर अगला चरित्र मुद्रित हो। यदि ऐसा नहीं है, तो आप केवल (char *)चरित्र को इंगित करने के लिए मुद्रित होने के लिए पास क्यों नहीं करते हैं, या यहां तक ​​कि केवल चरित्र को ही पास करते हैं?


अच्छा उत्तर; मैं इस बात को सीधे रखने के लिए सबसे आसान तरीका मानता हूं कि इस बारे में सोचना है कि क्या कोई सी वस्तु है जो सरणी का पता रखती है, अर्थात एक सूचक वस्तु जिसे आप प्राप्त करने के लिए पता ले सकते हैं char **। सरणी चर / बस वस्तुओं हैं सरणी, का पता किया जा रहा है निहित, नहीं जमा हो कहीं भी साथ। एक पॉइंटर वैरिएबल के साथ इनका उपयोग करने के लिए अप्रत्यक्ष स्तर का कोई अतिरिक्त स्तर नहीं है, जो अन्य भंडारण की ओर इशारा करता है।
पीटर कॉर्ड्स

0

जाहिर है, के पते testलेने के रूप में ही है के पते लेने के रूप में test[0]:

#include <stdio.h>
#include <stdlib.h>

static void printchar(char **x)
{
    printf("[printchar] Address of pointer to pointer: %p\n", (void *)x);
    printf("[printchar] Address of pointer: %p\n", (void *)*x);
    printf("Test: %c\n", **x);
}

int main(int argc, char *argv[])
{
    char test[256];
    char *test2 = malloc(256);

    printf("[main] Address of test: %p\n", (void *)test);
    printf("[main] Address of the address of test: %p\n", (void *)&test);
    printf("[main] Address of test2: %p\n", (void *)test2);
    printf("[main] Address of the address of test2: %p\n", (void *)&test2);

    test[0] = 'B';
    test2[0] = 'A';

    printchar(&test2);            // works
    printchar(&test);   // crashes because *x in printchar() has an invalid pointer

    free(test2);

    return 0;
}

संकलित करें और चलाएं:

forcebru$ clang test.c -Wall && ./a.out
test.c:25:15: warning: incompatible pointer types passing 'char (*)[256]' to
      parameter of type 'char **' [-Wincompatible-pointer-types]
    printchar(&test);   // crashes because *x in printchar() has an inva...
              ^~~~~
test.c:4:30: note: passing argument to parameter 'x' here
static void printchar(char **x)
                             ^
1 warning generated.
[main] Address of test: 0x7ffeeed039c0
[main] Address of the address of test: 0x7ffeeed039c0 [THIS IS A PROBLEM]
[main] Address of test2: 0x7fbe20c02aa0
[main] Address of the address of test2: 0x7ffeeed039a8
[printchar] Address of pointer to pointer: 0x7ffeeed039a8
[printchar] Address of pointer: 0x7fbe20c02aa0
Test: A
[printchar] Address of pointer to pointer: 0x7ffeeed039c0
[printchar] Address of pointer: 0x42 [THIS IS THE ASCII CODE OF 'B' in test[0] = 'B';]
Segmentation fault: 11

इसलिए विभाजन के दोष का अंतिम कारण यह है कि यह कार्यक्रम निरपेक्ष पते को बाधित करने की कोशिश करेगा 0x42 (जिसे भी जाना जाता है 'B') को, जिसे आपके कार्यक्रम को पढ़ने की अनुमति नहीं है, ।

हालांकि एक अलग संकलक / मशीन के साथ पते अलग होंगे: इसे ऑनलाइन आज़माएं! , लेकिन आप फिर भी इसे किसी कारण से प्राप्त कर सकते हैं:

[main] Address of test: 0x7ffd4891b080
[main] Address of the address of test: 0x7ffd4891b080  [SAME ADDRESS!]

लेकिन विभाजन की गलती के कारण पता अलग हो सकता है:

[printchar] Address of pointer to pointer: 0x7ffd4891b080
[printchar] Address of pointer: 0x9c000000942  [WAS 0x42 IN MY CASE]

1
का पता लेना testवैसा ही नहीं है जैसा कि पता लेने का है test[0]। पूर्व में टाइप होता है char (*)[256], और बाद में टाइप होता है char *। वे संगत नहीं हैं, और सी मानक उन्हें अलग-अलग प्रतिनिधित्व करने की अनुमति देता है।
एरिक पोस्टपिसिल

जब एक पॉइंटर को प्रारूपित किया जाता है %p, तो इसे void *(फिर से संगतता और प्रतिनिधित्व के कारणों के लिए) में परिवर्तित किया जाना चाहिए ।
एरिक पोस्टपिसिल

1
printchar(&test);आपके लिए दुर्घटना हो सकती है, लेकिन व्यवहार सी मानक द्वारा परिभाषित नहीं है, और लोग अन्य परिस्थितियों में अन्य व्यवहार का निरीक्षण कर सकते हैं।
एरिक पोस्टपिसिल

पुन: "विभाजन दोष का अंतिम कारण यह है कि यह कार्यक्रम 0x42 ('बी' के रूप में भी जाना जाता है) के पते को पूरी तरह से कम करने की कोशिश करेगा, जो संभवतः ओएस द्वारा कब्जा कर लिया गया है।": यदि कोई खंड दोष पढ़ने का प्रयास कर रहा है। एक स्थान, इसका मतलब है कि वहां कुछ भी मैप नहीं किया गया है, न कि यह ओएस द्वारा कब्जा कर लिया गया है। (सिवाय वहाँ कुछ मैप किया जा सकता है के रूप में, कहते हैं, केवल-पढ़ने की अनुमति के साथ निष्पादित, लेकिन यह संभावना नहीं है।)
एरिक Postpischil

1
&test == &test[0]सी 2018 6.5.9 2 में बाधाओं का उल्लंघन करता है क्योंकि प्रकार संगत नहीं हैं। सी मानक को इस उल्लंघन का निदान करने के लिए एक कार्यान्वयन की आवश्यकता है, और परिणामी व्यवहार सी मानक द्वारा परिभाषित नहीं है। इसका मतलब है कि आपका संकलक कोड का मूल्यांकन कर उन्हें समान होने का उत्पादन कर सकता है, लेकिन दूसरा संकलक नहीं हो सकता है।
एरिक पोस्टपिसिल

-4

का प्रतिनिधित्व char [256]कार्यान्वयन पर निर्भर है। यह जैसा होना चाहिए वैसा नहीं है char *

अपरिभाषित व्यवहार से पैदावार &testके प्रकार की कास्टिंग ।char (*)[256]char **

कुछ संकलक के साथ, यह वह कर सकता है जो आप अपेक्षा करते हैं, दूसरों पर नहीं।

संपादित करें:

9.2.1 gcc के साथ परीक्षण करने के बाद, ऐसा प्रतीत होता है कि printchar((char**)&test)वास्तव test में मूल्य डाली के रूप में गुजरता है char**। यह ऐसा है जैसे निर्देश था printchar((char**)test)। में printcharसमारोह, xसरणी परीक्षण, नहीं पहले अक्षर के लिए एक डबल सूचक के पहले चार के लिए सूचक है। एक डबल डे-संदर्भx खंड दोष में परिणाम क्योंकि सरणी के 8 पहले बाइट्स एक वैध पते के अनुरूप नहीं हैं।

मुझे सटीक समान व्यवहार मिलता है और परिणाम जब क्लैग 9.0.0-2 के साथ कार्यक्रम को संकलित करता है।

इसे एक कंपाइलर बग के रूप में माना जा सकता है, या एक अपरिभाषित व्यवहार का परिणाम हो सकता है जिसका परिणाम विशिष्ट हो सकता है।

एक और अप्रत्याशित व्यवहार यह है कि कोड

void printchar2(char (*x)[256]) {
    printf("px: %p\n", *x);
    printf("x: %p\n", x);
    printf("c: %c\n", **x);
}

आउटपुट है

px: 0x7ffd92627370
x: 0x7ffd92627370
c: A

अजीब व्यवहार है कि xऔर*x एक ही मूल्य है।

यह एक संकलक बात है। मुझे संदेह है कि यह भाषा द्वारा परिभाषित किया गया है।


1
क्या आपका मतलब char (*)[256]कार्यान्वयन-निर्भरता का प्रतिनिधित्व है? का प्रतिनिधित्व char [256]इस प्रश्न में प्रासंगिक नहीं है - यह केवल बिट्स का एक गुच्छा है। लेकिन, भले ही आप एक सरणी के लिए एक सूचक का प्रतिनिधित्व एक सूचक के लिए एक सूचक के प्रतिनिधित्व से अलग है, यह भी बात याद आती है। यहां तक ​​कि अगर उनके पास एक ही प्रतिनिधित्व है, तो ओपी का कोड काम नहीं करेगा, क्योंकि एक पॉइंटर को पॉइंटर को दो बार डीफर्म्ड किया जा सकता है, जैसा कि अंदर किया गया है printchar, लेकिन पॉइंटर एक अरेंजमेंट के बावजूद, किसी भी प्रतिनिधित्व के बिना नहीं हो सकता है।
एरिक पोस्टपिसिल

@EricPostpischil से कलाकारों char (*)[256]को char **संकलक द्वारा स्वीकार किया जाता है, लेकिन अपेक्षित परिणाम उपज नहीं है, क्योंकि एक char [256]एक के समान नहीं है char *। मैंने माना, एन्कोडिंग अलग है, अन्यथा यह अपेक्षित परिणाम देगा।
chmike

मुझे नहीं पता कि आप "अपेक्षित परिणाम" से क्या मतलब है। परिणाम क्या होना चाहिए इसका सी मानक में एकमात्र विनिर्देश है, यदि संरेखण के लिए अपर्याप्त है char **, तो व्यवहार अपरिभाषित है, और, अन्यथा, यदि परिणाम वापस बदल दिया जाता है char (*)[256], तो यह मूल सूचक के बराबर तुलना करता है। "अपेक्षित परिणाम" से, आपका मतलब यह हो सकता है कि, यदि (char **) &testइसे आगे में बदल दिया जाए char *, तो यह बराबर हो जाता है &test[0]। यह उन कार्यान्वयनों में एक अप्रत्याशित परिणाम नहीं है जो एक फ्लैट एड्रेस स्पेस का उपयोग करते हैं, लेकिन यह विशुद्ध रूप से प्रतिनिधित्व का मामला नहीं है।
एरिक पोस्टपिसिल

2
इसके अलावा, "कास्टिंग और प्रकार चार (*) का परीक्षण [256] से चार ** तक अपरिभाषित व्यवहार करता है।" सही नहीं है। C 2018 6.3.2.3 7 एक पॉइंटर को ऑब्जेक्ट टाइप करने के लिए किसी अन्य पॉइंटर को ऑब्जेक्ट टाइप में बदलने की अनुमति देता है। यदि पॉइंटर को संदर्भित प्रकार (के लिए संदर्भित प्रकार ) के लिए ठीक से संरेखित नहीं किया गया char **है char *, तो व्यवहार अपरिभाषित है। अन्यथा, रूपांतरण परिभाषित किया गया है, हालांकि मूल्य केवल मेरी टिप्पणी के अनुसार आंशिक रूप से परिभाषित किया गया है।
एरिक पोस्टपिसिल

char (*x)[256]जैसी बात नहीं है char **x। एक ही पॉइंटर मान का कारण xऔर *xप्रिंट यह है कि xयह सरणी के लिए केवल एक पॉइंटर है। आपका *x सरणी है , और एक सूचक संदर्भ में इसका उपयोग सरणी के पते पर वापस हो जाता है । वहाँ कोई कंपाइलर बग (या क्या (char **)&testकरता है), बस थोड़ा मानसिक जिम्नास्टिक यह पता लगाने के लिए आवश्यक है कि किस प्रकार के साथ चल रहा है। (cdecl यह समझाता है कि "x को संकेतक के रूप में x 256 वर्ण के लिए घोषित करें")। यहां तक ​​कि char*एक char**यूबी के ऑब्जेक्ट-प्रतिनिधित्व तक पहुंचने के लिए उपयोग नहीं किया जाता है; यह कुछ भी उर्फ ​​कर सकता है।
पीटर कॉर्ड्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.