首页 > 解决方案 > std::string::operator=(char): 哪个编译器是对的?以及如何测试这种错误?

问题描述

我在代码中遇到了这个奇怪的错误。该错误本身很常见...由于我打开了一个测试用例,因此我决定对其进行测试...我发现很难测试这种情况。

这是代码的概述:

#include <string>
#include <cstring>
#include <iostream>

// -------------------------------------------------------------------------
// simulating what is the C driver FYI, it's the alsa sound driver, 
// but that's not much relevant

char* some_text = nullptr;

void init_text()
{
    some_text = strdup("some text");
}

int get_card_name(int, char** x)
{
    *x = some_text;
    return 0;
}

//  end 'driver' code  
// -------------------------------------------------------------------------

int main()
{
    int i = 0;
    char* name;
    std::string s;

     // my original bug...  &"à! happens.
     s = get_card_name(i, &name);

     // Should have read:
     // if (!get_card_name(i, &name))
     //    s = name;

    // simulating EXPECT_FALSE(s.empty());
    std::cout << "s.empty()      : " << s.empty() << '\n';

    // simulating EXPECT_NE(s.length(), 0);
    std::cout << "s.length()     : " << s.length() << '\n';

    // simulating EXPECT_NE(s, "");
    std::cout << "(s == \"\")      : " << (s == "") << '\n';

    // a trace to check contents:
    std::cout << "s              : \"" << s << "\"\n";

    if (!s.empty())
        std::cout << "int(s.front()) : " << (int)s.front() << '\n';

    free(some_text);
    return s.length();
}

它看起来很简单,但这是 gcc 10.2 -Wall 的输出,但没有警告:(:

s.empty()      : 0
s.length()     : 1
(s == "")      : 0
s              : ""
int(s.front()) : 0

使用 clang 11.0.1 -Wall,但没有警告 :( :

s.empty()      : 0
s.length()     : 1
(s == "")      : 0
s              : ""
int(s.front()) : 0

并且使用 msvc 19.28 -Wall,以及超过 100 行警告 :( :

s.empty()      : 1
s.length()     : 0
(s == "")      : 1
s              : ""

事实证明,我的这个相当微不足道的错误很难测试,至少对于 gcc 和 clang 而言。想到三个问题...

您也可以在这里找到代码:https ://godbolt.org/z/9WhaGzTz8

[编辑] 作为思考的食物......

从上面的代码中获取字符串 s ......因为我主要关心的是用于检测生产代码中的错误的正确单元测试。

gcc 和 clang 报告

 (s == s.c_str()) is false

这确实抓住了错误,但不平等看起来很奇怪......这是否意味着某些东西在标准方面被破坏了?

标签: c++unit-testingstd

解决方案


哪个编译器是对的?

以下输出是正确的:

s.empty()      : 0
s.length()     : 1
(s == "")      : 0
s              : ""
int(s.front()) : 0

s = get_card_name(i, &name);应该调用basic_string& operator=( CharT ch );它应该导致包含单个字符对象的字符串。在这种情况下,值为 0,这意味着字符串将包含一个空终止符(除了字符串内容之后的空终止符)。

我们如何防止这种“简单”的错误?

直接初始化变量,而不是稍后分配。这通常是最佳实践,在这种情况下会保护您,因为 string 没有接受单个整数的构造函数。

std::string s = get_card_name(); // ill-formed

还有一些编译器选项可以警告隐式转换:

warning: conversion from 'int' to 'char' may change value [-Wconversion]

这样的选项通常过于嘈杂而无法无条件启用,但当您知道存在需要定位的错误时,它可能偶尔有用。


也避免不必要的“输出”参数。在这种情况下:

char* get_card_name()
{
    return some_text;
}

std::string s = get_card_name(); // works as expected

当然,这更多是关于如何编写 API,而不是关于如何处理难以使用的 API。有了这些,你需要仔细的勤奋。

实现一个更易于使用的包装 API 是一个好主意,这样就不需要直接使用 C 风格的 API。


gcc 和 clang stl 实现是罪魁祸首吗?我应该报告错误吗?

不,它们工作正常。


推荐阅读