"+ + C++中的东西

wly*_*les 59 c++ string

我的代码中发生了一些非常怪异的事情.我相信我已将其追踪到标有"here"的部分(代码当然是简化的):

std::string func() {
    char c;
    // Do stuff that will assign to c
    return "" + c; // Here
}
Run Code Online (Sandbox Code Playgroud)

当我尝试cout这个函数的结果时会发生各种各样的事情.我想我甚至设法获得了一些基础C++文档,还有许多分段错误.我很清楚,这在C++中不起作用(我现在使用stringstream转换string),但我想知道原因.在使用了大量的C#并且没有使用C++之后,这给我带来了很大的痛苦.

dyp*_*dyp 92

  • ""是一个字符串文字.那些类型为N的数组const char.此特定字符串文字是1const char数组,一个元素是空终止符.

  • 数组很容易衰减成指向第一个元素的指针,例如在需要指针的表达式中.

  • lhs + rhs没有为数组定义为lhs和整数为rhs.但它被定义为指针作为ls和整数作为rhs,使用通常的指针算法.

  • char 是C++核心语言中的整数数据类型(即,视为整数).

==> 字符串文字+字符因此被解释为指针+整数.

表达式"" + c大致相当于:

static char const lit[1] = {'\0'};
char const* p = &lit[0];
p + c // "" + c is roughly equivalent to this expression
Run Code Online (Sandbox Code Playgroud)

你回来了std::string.表达式"" + c产生一个指针const char.的构造std::string期望一个const char*希望它是一个指向空终止字符数组.

如果c != 0,则表达式"" + c导致未定义的行为:

  • 因为c > 1,指针算术会产生未定义的行为.指针算法仅在数组上定义,如果结果是同一数组的元素.

  • 如果char已签名,则c < 0出于同样的原因生成未定义的行为.

  • 因为c == 1,指针算术不会产生未定义的行为.这是一个特例; 允许指向一个元素超过数组的最后一个元素(但不允许使用它指向的内容).它仍然会导致未定义的行为,因为std::string此处调用的构造函数要求其参数是指向有效数组(以及以null结尾的字符串)的指针.一个过去的最后一个元素不是数组本身的一部分.违反此要求也会导致UB.


现在可能发生的是,构造函数std::string通过搜索数组中等于的第一个字符来尝试确定传递它的以null结尾的字符串的大小'\0':

string(char const* p)
{
    // simplified
    char const* end = p;
    while(*end != '\0') ++end;
    //...
}
Run Code Online (Sandbox Code Playgroud)

这将产生访问冲突,或者它创建的字符串包含"垃圾".编译器也可能假定这种未定义的行为永远不会发生,并且做一些有趣的优化会导致奇怪的行为.


顺便说一句,clang ++ 3.5为这个片段发出了一个很好的警告:

警告:在字符串中添加'char'不会附加到字符串[-Wstring-plus-int]

return "" + c; // Here
       ~~~^~~
Run Code Online (Sandbox Code Playgroud)

注意:使用数组索引来消除此警告

  • 所以基本上是一堆隐含的转换.好的,谢谢你的意见! (3认同)
  • @SimonLehmann是的,在`c == 1`的情况下,UB来自调用`std :: string`的ctor,其中的东西不是指向以空字符结尾的字符串的指针(这意味着指向有效数组的指针).也就是说,对于`c> 1`,有另一个UB源比ctor调用早,但在任何情况下都有'c> 0'的UB. (2认同)

Ben*_*igt 26

关于编译器如何解释这段代码有很多解释,但你可能想知道的是你做错了什么.

您似乎期待这种+行为std::string.问题是这两个操作数实际上都不是std::string.C++查看操作数的类型,而不是表达式的最终类型(这里是返回类型std::string)来解决重载.如果它看不到,它将不会选择std::string版本.+std::string

如果您对操作符有特殊行为(或者您编写了它,或者得到了一个提供它的库),则该行为仅在至少有一个操作数具有类类型(或对类类型的引用以及用户定义的枚举计数)时适用太).

如果你写的

std::string("") + c
Run Code Online (Sandbox Code Playgroud)

要么

std::string() + c
Run Code Online (Sandbox Code Playgroud)

要么

""s + c // requires C++14
Run Code Online (Sandbox Code Playgroud)

然后你会得到std::stringoperator + 的行为.

(注意,这些都不是真正好的解决方案,因为它们都可以制作std::string可以避免的短期实例std::string(1, c))

功能也是如此.这是一个例子:

std::complex<double> ipi = std::log(-1.0);
Run Code Online (Sandbox Code Playgroud)

您将收到运行时错误,而不是预期的虚数.那是因为编译器不知道它应该在这里使用复数对数.重载仅查看参数,参数是实数(double实际上是类型).

运算符重载ARE函数并遵守相同的规则.


Vla*_*cow 9

这个退货声明

return "" + c;
Run Code Online (Sandbox Code Playgroud)

已验证.使用所谓的指针算法.字符串文字""被转换为指向其第一个字符的指针(在这种情况下为其终止零),并且存储在c中的整数值被添加到指针中.所以表达的结果

"" + c
Run Code Online (Sandbox Code Playgroud)

有类型 const char *

类std :: string具有接受类型参数的转换构造函数const char *.问题是这个指针可以指向超出字符串文字.因此该函数具有未定义的行为.

我觉得使用这个表达没有任何意义.如果你想根据一个你可以写的字符来构建一个字符串

return std::string( 1, c );
Run Code Online (Sandbox Code Playgroud)

C++和C#之间的区别在于C#字符串文字的类型为System.String,它为字符串和字符(即C#中的unicode字符)重载了operator +.在C++中,字符串文字是常量字符数组,运算符+对于数组和整数的语义是不同的.数组转换为指向其第一个元素的指针,并使用指针算法.

它是标准类std :: string,它为字符重载了operator +.C++中的字符串文字不是此类的对象,类型为std :: string.