std :: string 매개 변수에 전달 된 0을 가장 잘 보호하는 방법은 무엇입니까?


20

방금 뭔가 방해가되었다는 것을 깨달았습니다. std::string매개 변수로 a 를 허용하는 메서드를 작성할 때마다 정의되지 않은 동작을 시작했습니다.

예를 들어,이 ...

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
char * 생성자에 0을 할당하는 것은 정의되지 않은 동작이므로, 실제로 호출자 결함입니다
ratchet freak

4
@ratchetfreak 나는 그것이 char* s = 0정의되지 않았다는 것을 몰랐다 . 나는 내 인생에서 (보통의 형태로 char* s = NULL) 수백 번 이상 보았습니다 . 백업에 대한 참조가 있습니까?
John Fitzpatrick

3
나는 std:string::string(char*)생성자를 의미했다
ratchet freak

2
귀하의 솔루션은 훌륭하다고 생각하지만 아무것도하지 않는 것이 좋습니다. :-) 메소드는 문자열을 취합니다. 유효한 조치를 호출 할 때 null 포인터를 전달하지 않습니다. 호출자가 실수로 이와 같은 메소드에 대해 null을 갑자기 벙글 링하는 경우 (오히려 오히려 예를 들어 로그 파일에보고되는 것보다 낫습니다. 컴파일 타임에 이것을 막을 수있는 방법이 있다면 그렇게해야합니다. 그렇지 않으면 그대로 둡니다. 이모. (BTW, 당신은 const std::string&그 매개 변수를 취할 수 없습니다 확신 합니까 ...?)
Grimm The Opiner

답변:


21

난 당신이 자신을 보호해야한다고 생각하지 않습니다. 발신자 측에서는 정의되지 않은 동작입니다. 당신 std::string::string(nullptr)이 아닙니다. 전화하는 발신자입니다 . 허용되지 않는 것입니다. 컴파일러는 컴파일 할 수 있지만 정의되지 않은 다른 동작도 컴파일 할 수 있습니다.

같은 방법으로 "널 참조"를 얻는 것입니다 :

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

널 포인터를 역 참조하는 사람이 UB를 작성하고이를 담당합니다.

또한 정의되지 않은 동작이 발생하지 않았다고 가정 할 수있는 옵티마이 저가 전체 권한을 갖기 때문에 정의되지 않은 동작이 발생한 후에는 자신을 보호 할 수 없으므로c_str() null 인지 확인하는 것이 최적화 될 수 있습니다.


똑같은 것을 말하는 아름답게 쓰여진 의견이 있으므로, 당신은 옳 아야합니다. ;-)
Ommer Grimm

2
여기의 문구는 Machiavelli가 아니라 Murphy로부터 보호됩니다. 잘 정통한 악의적 인 프로그래머는 충분히 열심히 노력하면 null 참조를 생성하기 위해 잘못된 형식의 객체를 보내는 방법을 찾을 수 있지만, 자체적으로 수행해야하며 실제로 원할 경우 발로 직접 촬영할 수도 있습니다. 실수로 실수를 방지하는 것이 가장 좋습니다. 이 경우 누군가 실수로 char * s = 0을 전달하는 경우는 거의 없습니다. 잘 구성된 문자열을 요청하는 함수에.
YoungJohn

2

아래 코드는의 명시 적 전달에 대한 컴파일 실패 와 with value의 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

또한 몇 년 전에이 문제가 발생하여 실제로 매우 무서운 것으로 나타났습니다. a를 전달 nullptr하거나 실수로 0 값을 가진 int를 전달하면 발생할 수 있습니다 .

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)
Bryan Chen

1

메소드의 서명을 다음과 같이 변경하는 것은 어떻습니까?

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

그렇게하면 호출자가 문자열을 호출하기 전에 문자열을 작성해야하며 걱정하는 어색함은 메소드에 도달하기 전에 문제를 일으켜 다른 사람들이 지적한 내용이 호출자의 오류가 아니라는 것을 분명히합니다.


그래 const string& s, 나는 그것을 만들어야했다 , 나는 실제로 잊었다. 그러나 그럼에도 불구하고 여전히 정의되지 않은 행동에 취약하지 않습니까? 발신자는 여전히을 전달할 수 0있습니까?
John Fitzpatrick

2
비 const 참조를 사용하면 임시 객체가 허용되지 않으므로 호출자가 더 이상 0을 전달할 수 없습니다. 그러나 임시 객체는 허용되지 않기 때문에 라이브러리를 사용하는 것이 훨씬 성 가시고 const 정확성을 포기합니다.
Josh Kelley

예전에 내가 그것을 저장할 수 있도록했고, 전달에는 변환 또는 일시적인 존재가 없다고 그러므로 보장 클래스에 const가 아닌 참조 매개 변수를 사용.
CashCow

1

과부하에 int매개 변수 를 제공하는 것은 어떻습니까?

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

private:
    void myMethod(int);

과부하를 정의 할 필요조차 없습니다. 전화를 걸려 고 myMethod(0)하면 링커 오류가 발생합니다.


1
이것은이 질문의 코드에 대해 보호하지 않습니다 0char*유형입니다.
벤 Voigt

0

를 사용하여 첫 번째 코드 블록에서 메소드를 호출하면 절대로 호출되지 않습니다 (char *)0. C ++은 단순히 문자열을 만들려고 시도하고 예외를 throw합니다. 해봤 어?

#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 :: string을 구성하는 것은 정의되지 않은 동작입니다.
ratchet freak

2
A A 좋은 날 세그먼트 폴트로 프로그램을 충돌합니다 널 역 참조와 같은 EXC_BAD_ACCESS 예외 소리
래칫 괴물

@ vy32 내가 작성한 코드 중 일부는 내가 std::string호출자가 아닌 다른 프로젝트에서 사용하는 라이브러리로 들어갑니다. 상황을 정상적으로 처리하고 호출자에게 예외를 주면 프로그램을 충돌시키지 않고 잘못된 인수를 전달했음을 알리는 방법을 찾고 있습니다. (물론, 발신자가 예외를 처리하지 못하고 프로그램이 중단 될 수 있습니다.)
John Fitzpatrick

2
@JohnFitzpatrick 당신은 std :: string에 전달 된 nullpointer로부터 자신을 보호 할 수 없을 것입니다. 표준 커뮤니티가 정의되지 않은 행동 대신 예외가되도록 설득 할 수 없다면
ratchet freak

@ratchetfreak 나는 그것이 내가 찾고있는 대답이라고 생각합니다. 기본적으로 나는 자신을 보호해야합니다.
John Fitzpatrick

0

char *가 잠재적으로 널 포인터가 될지 걱정되는 경우 (예 : 외부 C API에서 리턴) std :: string 대신 const char * 버전의 함수를 사용하는 것이 정답입니다. 즉

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

물론 그것들을 사용하려면 std :: string 버전이 필요합니다.

일반적으로 외부 API 및 마샬링 인수에 대한 호출을 유효한 값으로 분리하는 것이 가장 좋습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.