如何最好地保护传递给std :: string参数的0?


20

我刚刚意识到一些令人不安的事情。每次我编写一个接受std::string参数为a 的方法时,我都会向不确定的行为敞开大门。

例如,这...

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

可以这样称呼

char* s = 0;
myMethod(s);

...而且我无能为力(我知道)无法阻止它。

所以我的问题是:某人如何捍卫自己呢?

想到的唯一方法是,始终编写接受a 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属性?
梅森·惠勒2013年

6
仅将0分配给char *构造函数是未定义的行为,因此,这实际上是调用者的错
棘手怪胎

4
@ratchetfreak我不知道那char* s = 0是不确定的。我一生中至少见过数百次(通常以形式char* s = NULL)。您是否有参考资料以证明这一点?
John Fitzpatrick

3
我的意思是std:string::string(char*)构造函数
棘手怪胎

2
我认为您的解决方案很好,但是您应该考虑完全不做任何事情。:-)您的方法很清楚地采用了一个字符串,在调用它为有效操作时绝不会传递空指针-如果调用者不小心将空值绑定到此类方法中,则该方法会在其上被炸得越早(相反)比在日志文件中报告更好)。 如果有一种方法可以防止在编译时出现这种情况,那么您应该这样做,否则我会保留它。恕我直言。(顺便说一句,您确定不能const std::string&为该参数取一个...吗?)
格林(Grimm)The Opiner 2013年

Answers:


21

我认为您不应该保护自己。在调用方这是未定义的行为。不是您,而是呼叫者std::string::string(nullptr),这是不允许的。编译器可以对其进行编译,但也可以对其他未定义的行为进行编译。

同样的方法将得到“空引用”:

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

取消引用空指针的人正在制作UB并对其负责。

而且,您无法在发生未定义行为之后保护自己,因为优化器完全有权假设从未发生过未定义行为,因此c_str()可以优化检查是否为null的情况。


有一篇写得很漂亮的评论说的差不多,所以你一定是对的。;-)
格林(Grimm The Opiner)

2
这句话是为了防止墨菲,而不是马基雅维利。一个精通技术的恶意程序员可以找到方法,发送格式错误的对象,如果他们尽力而为,甚至可以创建空引用,但这是他们自己的工作,如果他们真的愿意,也可以让他们自己开枪。您可以期望做的最好的事情就是防止意外错误。在这种情况下,确实很少有人会不小心传入char * s = 0。要求一个格式正确的字符串的函数。
YoungJohn 2013年

2

以下代码给出了显式传递的编译失败0char*with的with 的运行时失败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
但是当for时仍然会遇到运行时错误,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
如果使用非常量引用,则调用者将无法再传递0,因为不允许使用临时对象。但是,使用您的库会变得更加烦人(因为不允许使用临时对象),并且您放弃了const正确性。
Josh Kelley 2013年

我曾经用一个非const引用参数的一类,我必须能够存储并因此确保没有传入转换或临时的存在。
CashCow

1

您如何提供重载作为int参数?

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

private:
    void myMethod(int);

您甚至不必定义重载。尝试呼叫myMethod(0)将触发链接器错误。


1
这不能防止问题中的代码0具有char*类型。
Ben Voigt

0

如果您尝试使用进行调用,则永远不会调用第一个代码块中的方法(char *)0。C ++只会尝试创建一个字符串,它将为您抛出异常。你试过了吗?

#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 将是未定义的行为
棘手怪胎

2
EXC_BAD_ACCESS异常听起来像是一个空取消引用,它将在美好的一天中因段错误而使您的程序崩溃
棘手怪胎

@ vy32我编写的接受的某些代码std::string进入了我不是调用者的其他项目使用的库中。我正在寻找一种方法来妥善处理这种情况,并通知调用方(也许有一个例外)他们传递了不正确的参数而不会导致程序崩溃。(确定,调用者可能无法处理我抛出的异常,并且程序仍然会崩溃。)
John Fitzpatrick

2
@JohnFitzpatrick,您将无法保护自己免受传递给std :: string的null指针的伤害,除非您可以说服标准礼让将其作为例外而不是未定义的行为
棘手的怪胎

@ratchetfreak在某种程度上,我认为这就是我一直在寻找的答案。所以基本上我必须保护自己。
John Fitzpatrick

0

如果您担心char *可能为空指针(例如,从外部C API返回),则答案是使用该函数的const char *版本而不是std :: string一个。即

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

当然,如果要允许使用std :: string版本,也将需要它们。

通常,最好将对外部API的调用和将参数编组为有效值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.