कैसे सबसे अच्छा रक्षा करने के लिए 0 से पारित करने के लिए एसटी :: स्ट्रिंग मापदंडों?


20

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

उदाहरण के लिए, यह ...

void myMethod(const std::string& s) { 
    /* Do something with s. */
}

... ऐसे कहा जा सकता है ...

char* s = 0;
myMethod(s);

... और इसे रोकने के लिए मैं कुछ भी नहीं कर सकता (जो मुझे पता है)।

तो मेरा सवाल यह है कि कोई इससे खुद का बचाव कैसे करता है?

दिमाग में आने वाला एकमात्र तरीका हमेशा किसी भी विधि के दो संस्करण लिखना है जो std::stringएक पैरामीटर के रूप में स्वीकार करता है , जैसे:

void myMethod(const std::string& s) {
    /* Do something. */
}

void myMethod(char* s) {
    if (s == 0) {
        throw std::exception("Null passed.");
    } else {
        myMethod(string(s));
    }
}

क्या यह एक सामान्य और / या स्वीकार्य समाधान है?

संपादित करें: कुछ ने कहा है कि मुझे एक पैरामीटर const std::string& sके std::string sरूप में स्वीकार करना चाहिए । मैं सहमत हूँ। मैंने पोस्ट को संशोधित किया। मुझे नहीं लगता कि हालांकि यह जवाब बदलता है।


1
टपका हुआ अमूर्त के लिए हुर्रे! मैं C ++ डेवलपर नहीं हूं, लेकिन क्या कोई कारण है कि आप स्ट्रिंग ऑब्जेक्ट की c_strसंपत्ति की जांच नहीं कर सके ?
मेसन व्हीलर

6
बस चार में काम कर रहा है * निर्माणकर्ता अपरिभाषित व्यवहार है, इसलिए यह वास्तव में कॉल करने वाले की गलती है
शाफ़्ट फ्रीक

4
@ratchetfreak मुझे नहीं पता था कि char* s = 0अपरिभाषित है। मैंने इसे अपने जीवन में कम से कम सौ बार (आमतौर पर char* s = NULL) के रूप में देखा है । क्या आपके पास इसे वापस करने का संदर्भ है?
जॉन फिट्जपैट्रिक

3
मेरा मतलब std:string::string(char*)कंस्ट्रक्टर से है
शाफ़्ट फ्रीक

2
मुझे लगता है कि आपका समाधान ठीक है, लेकिन आपको कुछ भी नहीं करने पर विचार करना चाहिए। :-) आपकी विधि काफी स्पष्ट रूप से एक स्ट्रिंग लेती है, किसी भी तरह से यह एक वैध कार्रवाई कहते समय एक अशक्त सूचक को पारित कर रहा है - इस घटना में कि एक कॉलर गलती से इस तरह से तरीकों के बारे में nulls bulling है, जितनी जल्दी यह उन पर चल रही है (बल्कि लॉग फ़ाइल में सूचना दी जा रही है, उदाहरण के लिए) बेहतर। यदि संकलन समय पर इसे रोकने का कोई तरीका था, तो आपको इसे करना चाहिए, अन्यथा मैं इसे छोड़ देता हूं। IMHO। (BTW, क्या आपको यकीन है कि आप const std::string&उस पैरामीटर के लिए नहीं ले जा सकते ...?)
ग्रिम द ओपिनर

जवाबों:


21

मुझे नहीं लगता कि आपको अपनी सुरक्षा करनी चाहिए। यह कॉलर के पक्ष में अपरिभाषित व्यवहार है। यह आप नहीं हैं, यह फोन करने वाला है std::string::string(nullptr), जो अनुमति नहीं है। संकलक इसे संकलित करने देता है, लेकिन यह अन्य अपरिभाषित व्यवहारों को भी संकलित करने देता है।

उसी तरह "शून्य संदर्भ" प्राप्त हो रहा होगा:

int* p = nullptr;
f(*p);
void f(int& x) { x = 0; /* bang! */ }

जो अशक्त सूचक को इंगित करता है वह यूबी बना रहा है और इसके लिए जिम्मेदार है।

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


एक सुंदर लिखित टिप्पणी है जो बहुत कुछ कहती है, इसलिए आपको सही होना चाहिए। ;-)
ग्रिम द ओपिनर

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

2

नीचे दिया गया कोड स्पष्ट पास के लिए एक संकलित विफलता 0और एक के लिए रन-टाइम विफलता देता हैchar* मान के साथ0

ध्यान दें कि मेरा मतलब यह नहीं है कि किसी को सामान्य रूप से ऐसा करना चाहिए, लेकिन इसमें कोई संदेह नहीं है कि ऐसे मामले हो सकते हैं जहां कॉलर त्रुटि से सुरक्षा उचित है।

struct Test
{
    template<class T> void myMethod(T s);
};

template<> inline void Test::myMethod(const std::string& s)
{
    std::cout << "Cool " << std::endl;
}

template<> inline void Test::myMethod(const char* s)
{
    if (s != 0)
        myMethod(std::string(s));
    else
    {
        throw "Bad bad bad";
    }
}

template<class T> inline void Test::myMethod(T  s)
{
    myMethod(std::string(s));
    const bool ok = !std::is_same<T,int>::value;
    static_assert(ok, "oops");
}

int main()
{
    Test t;
    std::string s ("a");
    t.myMethod("b");
    const char* c = "c";
    t.myMethod(c);
    const char* d = 0;
    t.myMethod(d); // run time exception
    t.myMethod(0); // Compile failure
}

1

मुझे कुछ साल पहले इस समस्या का भी सामना करना पड़ा था और मुझे यह वास्तव में बहुत डरावनी बात लगी। यह एक nullptrया गलती से मान के साथ एक इंट पास करके हो सकता है । यह वास्तव में बेतुका है:

std::string s(1); // compile error
std::string s(0); // runtime error

हालाँकि, अंत में इसने मुझे एक दो बार बग़ावत कर दिया। और हर बार मेरे कोड का परीक्षण करते समय यह तत्काल दुर्घटना का कारण बना। तो इसे ठीक करने के लिए रात-रात के सत्र की आवश्यकता नहीं होगी।

मुझे लगता है कि फंक्शन को ओवरलोड करना const char*एक अच्छा विचार है।

void foo(std::string s)
{
    // work
}

void foo(const char* s) // use const char* rather than char* (binds to string literals)
{
    assert(s); // I would use assert or std::abort in case of nullptr . 
    foo(std::string(s));
}

मैं चाहता हूँ कि एक अच्छा समाधान संभव था। हालांकि, वहाँ नहीं है।


2
लेकिन आप अभी भी रनटाइम त्रुटि प्राप्त करते हैं foo(0)और त्रुटि के लिए संकलन करते हैंfoo(1)
ब्रायन चेन

1

अपने तरीके के हस्ताक्षर को बदलने के बारे में:

void myMethod(std::string& s) // maybe throw const in there too.

इस तरह से कॉल करने वाले को कॉल करने से पहले एक स्ट्रिंग बनानी होती है, और जिस स्लोपनेस से आप चिंतित होते हैं, वह आपके तरीके के सामने आने से पहले समस्या पैदा कर देगा, जिससे स्पष्ट होगा कि दूसरों ने क्या इंगित किया है, कि यह कॉल करने वाले की त्रुटि है।


हाँ const string& s, मुझे इसे बनाना चाहिए था , मैं वास्तव में भूल गया था। लेकिन फिर भी, क्या मैं अभी भी अपरिभाषित व्यवहार के प्रति संवेदनशील नहीं हूं? कॉल करने वाला अभी भी पास कर सकता है 0, है ना?
जॉन फिट्जपैट्रिक

2
यदि आप एक नॉन-कास्ट संदर्भ का उपयोग करते हैं, तो कॉलर अब 0 पास नहीं कर सकता है, क्योंकि अस्थायी ऑब्जेक्ट की अनुमति नहीं होगी। हालाँकि, आपकी लाइब्रेरी का उपयोग करना बहुत अधिक कष्टप्रद हो जाएगा (क्योंकि अस्थायी वस्तुओं की अनुमति नहीं होगी) और आप कांस्टीट्यूशन सही कर रहे हैं।
जोश केली

मैंने एक बार एक नॉन-कॉस्ट रेफरेंस पैरामीटर का उपयोग उस वर्ग के लिए किया था, जहाँ मुझे इसे स्टोर करने में सक्षम होना था और यह सुनिश्चित करना था कि कोई रूपांतरण या अस्थायी पास न हो।
CashCow

1

कैसे के बारे में आप एक अधिभार लेता है पर एक intपैरामीटर लेता है ?

public:
    void myMethod(const std::string& s)
    { 
        /* Do something with s. */
    }    

private:
    void myMethod(int);

आप भी अधिभार को परिभाषित करने की जरूरत नहीं है। कॉल करने का प्रयास myMethod(0)लिंकर त्रुटि को ट्रिगर करेगा।


1
यह प्रश्न में कोड के खिलाफ सुरक्षा नहीं करता है, जहां 0एक char*प्रकार है।
बेन वोइग्ट

0

यदि आप इसे कॉल करने का प्रयास करते हैं, तो पहले कोड ब्लॉक में आपका तरीका कभी नहीं कहा जाएगा (char *)0। सी ++ बस एक स्ट्रिंग बनाने की कोशिश करेगा और यह आपके लिए अपवाद फेंक देगा। क्या तुमने कोशिश की?

#include <cstdlib>
#include <iostream>

void myMethod(std::string s) {
    std::cout << "s=" << s << "\n";
}

int main(int argc,char **argv) {
    char *s = 0;
    myMethod(s);
    return(0);
}


$ g++ -g -o x x.cpp 
$ lldb x 
(lldb) run
Process 2137 launched: '/Users/simsong/x' (x86_64)
Process 2137 stopped
* thread #1: tid = 0x49b8, 0x00007fff99bf9812 libsystem_c.dylib`strlen + 18, queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x00007fff99bf9812 libsystem_c.dylib`strlen + 18
libsystem_c.dylib`strlen + 18:
-> 0x7fff99bf9812:  pcmpeqb (%rdi), %xmm0
   0x7fff99bf9816:  pmovmskb %xmm0, %esi
   0x7fff99bf981a:  andq   $15, %rcx
   0x7fff99bf981e:  orq    $-1, %rax
(lldb) bt
* thread #1: tid = 0x49b8, 0x00007fff99bf9812 libsystem_c.dylib`strlen + 18, queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x00007fff99bf9812 libsystem_c.dylib`strlen + 18
    frame #1: 0x000000010000077a x`main [inlined] std::__1::char_traits<char>::length(__s=0x0000000000000000) + 122 at string:644
    frame #2: 0x000000010000075d x`main [inlined] std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string(this=0x00007fff5fbff548, __s=0x0000000000000000) + 8 at string:1856
    frame #3: 0x0000000100000755 x`main [inlined] std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string(this=0x00007fff5fbff548, __s=0x0000000000000000) at string:1857
    frame #4: 0x0000000100000755 x`main(argc=1, argv=0x00007fff5fbff5e0) + 85 at x.cpp:10
    frame #5: 0x00007fff92ea25fd libdyld.dylib`start + 1
(lldb) 

देख? आपको किसी बात की चिंता करने की आवश्यकता नहीं है।

अब यदि आप इसे और अधिक सुंदर रूप से पकड़ना चाहते हैं, तो आपको बस उपयोग नहीं करना चाहिए char *, तो समस्या उत्पन्न नहीं होगी।


4
एक nullpointer के साथ एक std :: स्ट्रिंग निर्माण अपरिभाषित व्यवहार किया जाएगा
रैशे सनकी

2
EXC_BAD_ACCESS अपवाद एक अशांति की तरह लगता है, जो आपके कार्यक्रम को एक अच्छे दिन के साथ दुर्घटनाग्रस्त कर देगा
शाफ़्ट फ्रीक

@ vy32 कुछ कोड जो मैं लिखता हूं कि std::stringअन्य परियोजनाओं द्वारा उपयोग किए जाने वाले पुस्तकालयों में जाता है जहां मैं कॉलर नहीं हूं। मैं स्थिति को इनायत से संभालने और कॉल करने वाले (शायद अपवाद के साथ) को सूचित करने का एक तरीका ढूंढ रहा हूं कि उन्होंने प्रोग्राम को क्रैश किए बिना एक गलत तर्क पारित किया। (ठीक है, दी गई, फोन करने वाला मुझे फेंकने वाले अपवाद को नहीं संभाल सकता है और कार्यक्रम वैसे भी दुर्घटनाग्रस्त हो जाएगा।)
जॉन फिट्जपैट्रिक

2
@ जॉनफिटपैट्रिक आप अपने आप को एक nullpointer से std में पारित होने से बचाने के लिए सक्षम नहीं होने जा रहे हैं :: स्ट्रिंग जब तक आप मानकों की कॉमन को यह नहीं मान सकते कि यह अपरिभाषित व्यवहार के बजाय एक अपवाद हो सकता है
रैटचिरक फ्रीक

@ratchetfreak एक तरह से मुझे लगता है कि वह जवाब है जिसकी मुझे तलाश थी। इसलिए मूल रूप से मुझे अपनी रक्षा करनी होगी।
जॉन फिट्जपैट्रिक

0

यदि आप चार * संभावित रूप से अशक्त पॉइंटर्स (जैसे कि बाहरी सी एपीआई से रिटर्न) होने के बारे में चिंतित हैं, तो इसका उत्तर std :: string one के बजाय फंक्शन के एक const char * संस्करण का उपयोग करना है। अर्थात

void myMethod(const char* c) { 
    std::string s(c ? c : "");
    /* Do something with s. */
}

निश्चित रूप से आपको std :: string version की आवश्यकता होगी, यदि आप उन्हें उपयोग करने की अनुमति देना चाहते हैं।

सामान्य तौर पर बाहरी एपीआई और मार्शल तर्कों को मान्य मूल्यों में कॉल को अलग करना सबसे अच्छा है।

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