सी में, कोई इस तरह से घोषणा में एक स्ट्रिंग शाब्दिक का उपयोग कर सकता है:
char s[] = "hello";
या इस तरह:
char *s = "hello";
तो अंतर क्या है? मैं जानना चाहता हूं कि भंडारण अवधि के दौरान वास्तव में क्या होता है, दोनों संकलन और चलाने के समय पर।
सी में, कोई इस तरह से घोषणा में एक स्ट्रिंग शाब्दिक का उपयोग कर सकता है:
char s[] = "hello";
या इस तरह:
char *s = "hello";
तो अंतर क्या है? मैं जानना चाहता हूं कि भंडारण अवधि के दौरान वास्तव में क्या होता है, दोनों संकलन और चलाने के समय पर।
जवाबों:
यहाँ अंतर यह है कि
char *s = "Hello world";
मेमोरी के केवल-पढ़ने वाले भागों"Hello world"
में जगह लेगा , और इसके लिए एक संकेतक बना देगा जो इस मेमोरी पर किसी भी लेखन ऑपरेशन को अवैध बनाता है।s
करते हुए:
char s[] = "Hello world";
शाब्दिक स्ट्रिंग को केवल-पढ़ने वाली मेमोरी में रखता है और स्टैक पर नए आवंटित मेमोरी में स्ट्रिंग को कॉपी करता है। इस प्रकार बना रहा है
s[0] = 'J';
कानूनी।
"Hello world"
दोनों उदाहरणों में "मेमोरी के केवल-पढ़ने योग्य भागों" में है। सरणी के साथ उदाहरण वहाँ इंगित करता है, सरणी के साथ उदाहरण वर्ण तत्वों को कॉपी करता है।
char msg[] = "hello, world!";
प्रारंभिक डेटा अनुभाग में सिर्फ स्ट्रिंग समाप्त होता है। जब char * const
केवल-पढ़ने के लिए डेटा अनुभाग में समाप्त होने की घोषणा की जाती है। gcc-4.5.3
सबसे पहले, फ़ंक्शन तर्क में, वे बिल्कुल बराबर हैं:
void foo(char *x);
void foo(char x[]); // exactly the same in all respects
अन्य संदर्भों में, char *
एक सूचक आवंटित करता है, जबकि char []
एक सरणी आवंटित करता है। पूर्व मामले में स्ट्रिंग कहां जाती है, आप पूछते हैं? संकलक स्ट्रिंग शाब्दिक रूप से धारण करने के लिए एक स्थिर अनाम सरणी को गुप्त रूप से आवंटित करता है। इसलिए:
char *x = "Foo";
// is approximately equivalent to:
static const char __secret_anonymous_array[] = "Foo";
char *x = (char *) __secret_anonymous_array;
ध्यान दें कि आपको कभी भी इस सूचक के माध्यम से इस अनाम सरणी की सामग्री को संशोधित करने का प्रयास नहीं करना चाहिए; प्रभाव अपरिभाषित होते हैं (अक्सर दुर्घटना का अर्थ होता है):
x[1] = 'O'; // BAD. DON'T DO THIS.
सरणी सिंटैक्स का उपयोग करके इसे सीधे नई मेमोरी में आवंटित किया जाता है। इस प्रकार संशोधन सुरक्षित है:
char x[] = "Foo";
x[1] = 'O'; // No problem.
हालाँकि यह सरणी केवल तब तक रहती है जब तक कि इसका कंटैनिंग स्कोप न हो, इसलिए यदि आप किसी फ़ंक्शन में ऐसा करते हैं, तो इस ऐरे में पॉइंटर लौटाएं या लीक न करें - इसके साथ एक कॉपी बनाएं strdup()
या इसी तरह । यदि सरणी को वैश्विक दायरे में आवंटित किया गया है, तो निश्चित रूप से, कोई समस्या नहीं है।
यह घोषणा:
char s[] = "hello";
एक ऑब्जेक्ट बनाता है - char
आकार 6 की एक सरणी, जिसे s
मूल्यों के साथ आरंभीकृत किया जाता है 'h', 'e', 'l', 'l', 'o', '\0'
। यह सरणी स्मृति में कहाँ आवंटित की गई है, और यह कितने समय तक रहती है, यह इस बात पर निर्भर करता है कि घोषणा कहाँ दिखाई देती है। यदि घोषणा एक फ़ंक्शन के भीतर है, तो यह उस ब्लॉक के अंत तक जीवित रहेगा जब तक यह घोषित नहीं किया जाता है और लगभग निश्चित रूप से स्टैक पर आवंटित किया जाता है; यदि यह किसी फ़ंक्शन के बाहर है, तो संभवतः इसे "इनिशियलाइज़्ड डेटा सेगमेंट" के भीतर संग्रहित किया जाएगा जो कि प्रोग्राम चलने पर निष्पादन योग्य फ़ाइल से राइटेबल मेमोरी में लोड हो जाता है।
दूसरी ओर, यह घोषणा:
char *s ="hello";
दो ऑब्जेक्ट बनाता है:
char
मूल्यों से युक्त रों 'h', 'e', 'l', 'l', 'o', '\0'
है, जो कोई नाम नहीं है और है स्थिर भंडारण अवधि (जिसका अर्थ है कि यह कार्यक्रम के पूरे जीवन के लिए रहता है); तथाs
, जो उस अनाम, रीड-ओनली एरे में पहले वर्ण के स्थान के साथ आरम्भ होता है।अनाम रीड-ओनली सरणी आमतौर पर प्रोग्राम के "टेक्स्ट" सेगमेंट में स्थित होती है, जिसका अर्थ है कि यह डिस्क से रीड-ओनली मेमोरी में लोड होता है, कोड के साथ ही। s
मेमोरी में पॉइंटर चर का स्थान इस बात पर निर्भर करता है कि घोषणा कहाँ दिखाई देती है (पहले उदाहरण की तरह)।
char s[] = "hello"
मामले में, "hello"
बस एक इनिलाइज़र है जो कंपाइलर को बता रहा है कि ऐरे को कैसे इनिशियलाइज़ किया जाना चाहिए। टेक्स्ट सेगमेंट में इसके अनुरूप स्ट्रिंग हो सकती है या नहीं हो सकती है - उदाहरण के लिए, यदि s
स्टैटिक स्टोरेज की अवधि है, तो यह संभावना है कि "hello"
प्रारंभिक डेटा सेगमेंट में एकमात्र उदाहरण - ऑब्जेक्ट s
ही होगा। यहां तक कि अगर s
स्वचालित भंडारण की अवधि है, तो इसे एक कॉपी (जैसे। movl $1819043176, -6(%ebp); movw $111, -2(%ebp)
) के बजाय शाब्दिक दुकानों के अनुक्रम द्वारा आरंभ किया जा सकता है ।
.rodata
, जो लिंकर स्क्रिप्ट फिर उसी खंड में डंप करता है .text
। मेरा जवाब देखिए ।
char s[] = "Hello world";
शाब्दिक स्ट्रिंग को रीड-ओनली मेमोरी में रखा जाता है और स्टैक पर नए आवंटित मेमोरी में स्ट्रिंग को कॉपी करता है। लेकिन, आपका जवाब केवल वाचन में शाब्दिक स्ट्रिंग के बारे में बोलता है जो केवल मेमोरी में है और वाक्य का दूसरा भाग छोड़ता है जो कहता है copies the string to newly allocated memory on the stack
:। तो, क्या दूसरा भाग निर्दिष्ट नहीं करने के लिए आपका उत्तर अधूरा है?
char s[] = "Hellow world";
केवल एक इनिशियलाइज़र है और जरूरी नहीं कि इसे एक अलग रीड-ओनली कॉपी के रूप में संग्रहीत किया जाए। यदि s
स्थिर भंडारण अवधि है, तो स्ट्रिंग की एकमात्र प्रति के स्थान पर एक रीड-राइट सेगमेंट में होने की संभावना है s
, और फिर भी नहीं तो कंपाइलर लोड-तत्काल निर्देशों के साथ सरणी को आरम्भ करने के लिए चुन सकता है या कॉपी करने के बजाय इसी तरह केवल पढ़ने के लिए स्ट्रिंग से। मुद्दा यह है कि इस मामले में, इनिशियलर स्ट्रिंग में ही कोई रनटाइम उपस्थिति नहीं है।
घोषणाओं को देखते हुए
char *s0 = "hello world";
char s1[] = "hello world";
निम्नलिखित काल्पनिक स्मृति मानचित्र ग्रहण करें:
0x01 0x02 0x03 0x04 0x00008000: 'एच' 'ई' 'एल' 'एल' 0x00008004: 'ओ' '' 'डब्ल्यू' 'ओ' 0x00008008: 'r' 'l' 'd' 0x00 ... s0: 0x00010000: 0x00 0x00 0x80 0x00 s1: 0x00010004: 'h' 'e' 'l' 'l' 0x00010008: 'ओ' '' 'डब्ल्यू' 'ओ' 0x0001000C: 'r' 'l' 'd' 0x00
स्ट्रिंग शाब्दिक स्थैतिक भंडारण अवधि के साथ "hello world"
एक 12-तत्व सरणी char
( const char
C ++ में) है, जिसका अर्थ है कि जब प्रोग्राम शुरू होता है और कार्यक्रम समाप्त होने तक आवंटित किया जाता है तो इसके लिए मेमोरी आवंटित की जाती है। एक स्ट्रिंग शाब्दिक की सामग्री को संशोधित करने का प्रयास अपरिभाषित व्यवहार को आमंत्रित करता है।
रेखा
char *s0 = "hello world";
ऑटो स्टोरेज अवधि के साथ s0
एक पॉइंटर के रूप में परिभाषित करता है char
(जिसका अर्थ है कि चर s0
केवल उस गुंजाइश के लिए मौजूद है जिसमें इसे घोषित किया गया है) और स्ट्रिंग शाब्दिक ( इस उदाहरण में) के पते को कॉपी करता 0x00008000
है। ध्यान दें कि जब से s0
एक स्ट्रिंग शाब्दिक को अंक है, यह (उदाहरण के लिए, किसी भी समारोह है कि इसे संशोधित करने की कोशिश करेगा करने के लिए एक तर्क के रूप में इस्तेमाल नहीं किया जाना चाहिए strtok()
, strcat()
, strcpy()
, आदि)।
रेखा
char s1[] = "hello world";
ऑटो स्टोरेज अवधि के साथ ( s1
तत्व char
शाब्दिक से लंबाई लिया जाता है) के 12-तत्व सरणी के रूप में परिभाषित करता है और शाब्दिक की सामग्री को सरणी में कॉपी करता है । जैसा कि आप मेमोरी मैप से देख सकते हैं, हमारे पास स्ट्रिंग की दो प्रतियां हैं "hello world"
; अंतर यह है कि आप इसमें निहित स्ट्रिंग को संशोधित कर सकते हैं s1
।
s0
और s1
अधिकांश संदर्भों में विनिमेय हैं; यहाँ अपवाद हैं:
sizeof s0 == sizeof (char*)
sizeof s1 == 12
type of &s0 == char **
type of &s1 == char (*)[12] // pointer to a 12-element array of char
आप s0
भिन्न स्ट्रिंग शाब्दिक या किसी अन्य चर को इंगित करने के लिए चर को फिर से असाइन कर सकते हैं । आप s1
भिन्न सरणी को इंगित करने के लिए चर को पुन: असाइन नहीं कर सकते हैं ।
C99 N1256 ड्राफ्ट
चरित्र स्ट्रिंग शाब्दिक के दो अलग-अलग उपयोग हैं:
आरंभ करें char[]
:
char c[] = "abc";
यह "अधिक जादू" है, और 6.7.8 / 14 "प्रारंभिककरण" में वर्णित है:
वर्ण प्रकार का एक वर्ण वर्ण स्ट्रिंग शाब्दिक द्वारा प्रारंभ किया जा सकता है, वैकल्पिक रूप से ब्रेसिज़ में संलग्न है। चरित्र स्ट्रिंग शाब्दिक के क्रमिक वर्ण (यदि कमरे में या यदि सरणी अज्ञात आकार का है, तो समाप्त करने वाले अशक्त चरित्र सहित), सरणी के तत्वों को आरंभीकृत करते हैं।
तो यह सिर्फ एक शॉर्टकट है:
char c[] = {'a', 'b', 'c', '\0'};
किसी भी अन्य नियमित सरणी की तरह, c
संशोधित किया जा सकता है।
हर जगह: यह एक उत्पन्न करता है:
इसलिए जब आप लिखते हैं:
char *c = "abc";
यह समान है:
/* __unnamed is magic because modifying it gives UB. */
static char __unnamed[] = "abc";
char *c = __unnamed;
नोट से निहित कलाकारों char[]
के लिए char *
है, जो हमेशा कानूनी है।
फिर यदि आप संशोधित करते हैं c[0]
, तो आप संशोधित भी करते हैं __unnamed
, जो कि यूबी है।
यह 6.4.5 "स्ट्रिंग शाब्दिक" पर प्रलेखित है:
5 अनुवाद चरण 7 में, एक बाइट या मूल्य शून्य का कोड प्रत्येक मल्टीबीट चरित्र अनुक्रम में जोड़ा जाता है जो एक स्ट्रिंग शाब्दिक या शाब्दिक परिणाम होता है। मल्टीबाइट कैरेक्टर सीक्वेंस का उपयोग तब स्टैटिक स्टोरेज की अवधि और लंबाई को व्यवस्थित करने के लिए किया जाता है। चरित्र स्ट्रिंग शाब्दिकों के लिए, सरणी तत्वों में चार वर्ण होते हैं, और मल्टीबाइट वर्ण अनुक्रम के व्यक्तिगत बाइट्स के साथ आरंभिक होते हैं ...]]
6 यह स्पष्ट नहीं है कि क्या ये सरणियाँ अलग-अलग हैं बशर्ते कि उनके तत्वों में उचित मूल्य हों। यदि प्रोग्राम ऐसे सरणी को संशोधित करने का प्रयास करता है, तो व्यवहार अपरिभाषित है।
6.7.8 / 32 "प्रारंभिक" एक प्रत्यक्ष उदाहरण देता है:
उदाहरण 8: घोषणा
char s[] = "abc", t[3] = "abc";
"सादा" वर्ण सरणी वस्तुओं को परिभाषित करता है
s
औरt
जिनके तत्व वर्ण स्ट्रिंग शाब्दिक के साथ आरंभिक होते हैं।यह घोषणा समान है
char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };
सरणियों की सामग्री परिवर्तनीय हैं। दूसरी ओर, घोषणा
char *p = "abc";
p
प्रकार "पॉइंटर टू चार" के साथ परिभाषित करता है और इसे एक ऑब्जेक्ट के साथ इंगित करने के लिए टाइप करता है "वर्ण का प्रकार" लंबाई 4 के साथ जिसका तत्व एक चरित्र स्ट्रिंग शाब्दिक के साथ आरम्भ किया गया है। यदिp
सरणी की सामग्री को संशोधित करने के लिए उपयोग करने का प्रयास किया जाता है , तो व्यवहार अपरिभाषित है।
जीसीसी 4.8 x86-64 ईएलएफ कार्यान्वयन
कार्यक्रम:
#include <stdio.h>
int main(void) {
char *s = "abc";
printf("%s\n", s);
return 0;
}
संकलन और विघटित:
gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o
आउटपुट में शामिल हैं:
char *s = "abc";
8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp)
f: 00
c: R_X86_64_32S .rodata
निष्कर्ष: GCC char*
इसे .rodata
अनुभाग में संग्रहीत करता है , अंदर नहीं .text
।
हालांकि ध्यान दें कि डिफ़ॉल्ट लिंकर स्क्रिप्ट डालता है .rodata
और .text
उसी सेगमेंट में होता है, जिसने निष्पादित किया है, लेकिन कोई लिखित अनुमति नहीं है। इसके साथ मनाया जा सकता है:
readelf -l a.out
जिसमें है:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
यदि हम ऐसा ही करते हैं char[]
:
char s[] = "abc";
हमने प्राप्त किया:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
तो यह स्टैक में संग्रहीत (सापेक्ष %rbp
) हो जाता है ।
char s[] = "hello";
s
एक सरणी होने की घोषणा करता है, char
जो आरंक्षक (5 + 1 char
s) को पकड़ने के लिए काफी लंबा है और सरणी में दिए गए स्ट्रिंग शाब्दिक के सदस्यों को कॉपी करके सरणी को आरंभीकृत करता है।
char *s = "hello";
s
एक या एक से अधिक (इस मामले में अधिक) के लिए एक संकेतक होने की घोषणा करता है char
और इसे शाब्दिक युक्त एक निश्चित (केवल पढ़ने के लिए) स्थान पर इंगित करता है "hello"
।
s
में एक सूचक है const char
।
char s[] = "Hello world";
यहाँ, s
वर्णों की एक सरणी है, जिसे हम चाहें तो अधिलेखित किया जा सकता है।
char *s = "hello";
एक स्ट्रिंग शाब्दिक का उपयोग इन वर्ण ब्लॉक को मेमोरी में कहीं बनाने के लिए किया जाता है जिसे यह पॉइंटर s
इंगित कर रहा है। हम यहां उस वस्तु को फिर से असाइन कर सकते हैं जो इसे बदलकर इंगित कर रही है, लेकिन जब तक यह एक स्ट्रिंग शाब्दिक को वर्णों के ब्लॉक को इंगित करता है, जिसे यह इंगित नहीं किया जा सकता है।
एक अतिरिक्त के रूप में, कि मानते हैं, के रूप में केवल पढ़ने के प्रयोजनों के लिए दोनों के उपयोग के समान है, आप के साथ या तो अनुक्रमण द्वारा चार उपयोग कर सकते हैं []
या *(<var> + <index>)
प्रारूप:
printf("%c", x[1]); //Prints r
तथा:
printf("%c", *(x + 1)); //Prints r
जाहिर है, यदि आप करने का प्रयास करते हैं
*(x + 1) = 'a';
संभवत: आपको एक सेगमेंटेशन फ़ॉल्ट मिलेगा, क्योंकि आप रीड-ओनली मेमोरी एक्सेस करने की कोशिश कर रहे हैं।
x[1] = 'a';
जिसमें सेगफॉल्ट (साथ ही प्लेटफॉर्म पर निर्भर करता है)।
char *str = "Hello";
उपरोक्त सेट्स शाब्दिक मान "हैलो" को इंगित करने के लिए ऊपर है जो प्रोग्राम की बाइनरी इमेज में हार्ड-कोडेड है, जिसे केवल-पढ़ने के लिए मेमोरी के रूप में चिह्नित किया गया है, इसका मतलब है कि इस स्ट्रिंग शाब्दिक में कोई भी परिवर्तन अवैध है और यह विभाजन दोषों को फेंक देगा।
char str[] = "Hello";
स्टैक पर नव आवंटित मेमोरी में स्ट्रिंग को कॉपी करता है। इस प्रकार इसमें कोई भी परिवर्तन करने की अनुमति है और कानूनी है।
means str[0] = 'M';
"मेलो" के लिए स्ट्रिंग को बदल देगा।
अधिक जानकारी के लिए, कृपया इसी तरह के प्रश्न से गुजरें:
के मामले में:
char *x = "fred";
x एक अंतराल है - इसे सौंपा जा सकता है। लेकिन इस मामले में:
char x[] = "fred";
x एक लवल्यू नहीं है, यह एक प्रचलन है - आप इसे असाइन नहीं कर सकते।
x
एक गैर-परिवर्तनीय अंतराल है। हालांकि लगभग सभी संदर्भों में, यह अपने पहले तत्व के लिए एक संकेतक का मूल्यांकन करेगा, और वह मूल्य एक प्रतिद्वंद्विता है।
यहाँ टिप्पणियों की रोशनी में यह स्पष्ट होना चाहिए कि: char * s = "हैलो"; एक बुरा विचार है, और इसका उपयोग बहुत संकीर्ण दायरे में किया जाना चाहिए।
यह इंगित करने का एक अच्छा अवसर हो सकता है कि "कांस्ट शुद्धता" एक "अच्छी बात" है। जब भी और जहाँ भी आप कर सकते हैं, "कोड" कीवर्ड का उपयोग अपने कोड की रक्षा करने के लिए, "आराम से" कॉल करने वाले या प्रोग्रामर से करें, जो आमतौर पर पॉइंट में आने पर सबसे "आराम" होते हैं।
पर्याप्त मेलोड्रामा, यहां वह है जो "कॉन्स्ट" के साथ संकेत देने वाले को प्राप्त कर सकता है। (नोट: किसी को दाएं-बाएं सूचक की घोषणाओं को पढ़ना है।) पॉइंटर्स खेलते समय अपने आप को बचाने के 3 अलग-अलग तरीके हैं:
const DBJ* p means "p points to a DBJ that is const"
- अर्थात, DBJ ऑब्जेक्ट को p के माध्यम से नहीं बदला जा सकता है।
DBJ* const p means "p is a const pointer to a DBJ"
- अर्थात, आप p के माध्यम से DBJ ऑब्जेक्ट को बदल सकते हैं, लेकिन आप पॉइंटर p को स्वयं नहीं बदल सकते।
const DBJ* const p means "p is a const pointer to a const DBJ"
- अर्थात, आप सूचक p को स्वयं नहीं बदल सकते, और न ही p के द्वारा DBJ ऑब्जेक्ट को बदल सकते हैं।
संकलित कॉन्स्ट-एंट म्यूटेशन से संबंधित त्रुटियों को संकलन समय पर पकड़ा जाता है। कब्ज के लिए कोई रनटाइम स्पेस या स्पीड पेनल्टी नहीं है।
(मान लें कि आप C ++ कंपाइलर का उपयोग कर रहे हैं, निश्चित रूप से?)
--DBJ