简体   繁体   中英

std::string::operator=(char): Which compiler is right? And how to test for this kind of bug?

I tripped upon this strange bug in my code. The bug itself is quite common... Since I had a test case open, I decided to test for it... I found it was quite hard to test for this condition.

Here is an overview of the code:

#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();
}

It looks straightforward, but here is the output with gcc 10.2 -Wall, but no warnings:(:

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

With clang 11.0.1 -Wall, but no warnings:(:

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

And with msvc 19.28 -Wall, and over 100 lines of warnings:(:

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

This rather trivial, bug of mine turns out to be quite difficult to test for, at least for gcc and clang. Three questions come to mind...

  • which compiler is right?
  • Detecton is not so obvious. How can we protect against this kind of 'easy' bug?
  • Are the gcc and clang stl implementations to blame? Should I report a bug?

You will find the code here as well: https://godbolt.org/z/9WhaGzTz8

[EDIT] As food for thought...

Taking the string s from the code above... Since my main concern is corect unit testing for detecting bugs in production code.

gcc and clang report

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

This does catch the bug, but the inequality looks strange... Does this mean that something is broken with respect to the standard?

which compiler is right?

The following output is correct:

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

s = get_card_name(i, &name); should invoke basic_string& operator=( CharT ch ); which should result in the the string containing a single character object. In this case, the value is 0 which means that the string would contain a single null terminator (in addition to the null terminator after the content of the string).

How can we protect against this kind of 'easy' bug?

Initialise variables directly, instead of assigning later. It's usually the best practice and in this case would have protected you because string doesn't have a constructor accepting a single integer.

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

There are also compiler options to warn about implicit conversions:

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

Such option is usually too noisy to be enabled unconditionally, but it may be occasionally useful when you know there is a bug that you need to locate.


Also avoid "output" parameters when they are unnecessary. In this case:

char* get_card_name()
{
    return some_text;
}

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

Of course, this is more about how to write the API, not about how to deal with APIs that are difficult to use. With those, you need careful diligence.

It would be a good idea to implement an easier to use wrapper API so that the C-style API doesn't need to be used directly.


Are the gcc and clang stl implementations to blame? Should I report a bug?

No. They work correctly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM