c++ - 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 而言。想到三个问题...
- 哪个编译器是对的?
- Detecton不是那么明显。我们如何防止这种“简单”的错误?
- gcc 和 clang stl 实现是罪魁祸首吗?我应该报告错误吗?
您也可以在这里找到代码:https ://godbolt.org/z/9WhaGzTz8
[编辑] 作为思考的食物......
从上面的代码中获取字符串 s ......因为我主要关心的是用于检测生产代码中的错误的正确单元测试。
gcc 和 clang 报告
(s == s.c_str()) is false
这确实抓住了错误,但不平等看起来很奇怪......这是否意味着某些东西在标准方面被破坏了?
解决方案
哪个编译器是对的?
以下输出是正确的:
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 实现是罪魁祸首吗?我应该报告错误吗?
不,它们工作正常。
推荐阅读
- bash - 读取文件时嵌套脚本
- android - 从快捷方式创建意图
- java - 如何在 Android 中处理 BottomNavigationView 的索引
- c - 是什么导致我的数组充满了不需要的数字
- ruby-on-rails - Rails - Couldn't find Variant without an ID
- mysql - 谷歌大查询
- scala - 如何读取 Scala 序列化堆栈(来自 Spark)?
- python - ExcelWriter 写入时覆盖工作表
- java - JAVA - 同时使用 JPA 和 PreparedStatements 的坏习惯?
- powershell - 为什么在 -MaximumVersion 设置为较低版本的情况下导入时运行较新版本的 PowerShell 模块?