Main Content

Unnecessary use of std::string::c_str() or equivalent string methods

Instead of a std::string object, a string operation uses the C-string obtained from std::string functions including std::string::c_str, std::string::data(), std::string::at(), or std::string::operator[], resulting in inefficient code

Since R2020b

Description

This defect occurs when a string operation is performed by using a C-string pointer obtained from string functions such as std::string::c_str, std::string::data(), std::string::at(), and std::string::operator[]. For instance, this checker is raised when:

  • A new std::string or std::wstring is implicitly or explicitly constructed from the C-string obtained from a string function. This situation arises when a function expecting a const reference to the string encounters a const char* instead.

  • A new copy of a string object is created explicitly from the C-string obtained from a string function. Using the copy constructor is the more efficient way of copying the string object.

  • Certain std::string member functions are invoked by using the C-string obtained from a string function. Flagged functions include replace, append, assign, compare, and find. Using an std::string object directly to invoke std::string member functions is more efficient.

  • A user-defined function that is overloaded to accept either of the const char* or const std::string arguments is invoked by using a C-string pointer. It is more efficient to invoke the std::string overload of such a function. When a function is overloaded in this way, calling the const char* overload from the body of the const std::string overload by using the C-string pointer does not raise the defect.

  • A std::string_view object is constructed from a C-string obtained from a std::string object, either implicitly or explicitly. For example, it is inefficient to pass a C-string obtained from a std::string object to a function that accepts a std::string_view parameter. Calling the function using the std::string object is more efficient.

Risk

It is expensive and inefficient to use the C-string output of a std::string function when you can use an std::string object instead. An std::string object contains the length of the string. When you use a C-string instead of an std::string object, the constructor determines the length of the C-string by a linear search, resulting in inefficient code. Using the C-string is also often unnecessary. Consider this code:

void set_prop1(const char* str);
void set_prop2(const std::string& str);
void foo( std::string& str){
	//...
	set_prop1(str.c_str()); // Necessary
	//...
	set_prop2(str.c_str()); // Inefficient	
}
The function foo calls two different functions. Because the function set_prop1 requires a C-string as the input, using the str.c_str function is necessary to form the input to set_prop1. The function set_prop2 takes an std::string as an input. Instead of directly using str as an input to set_prop2, str.c_str is used, perhaps as a copy-paste mistake. The compiler implicitly constructs a new std::string object, which is identical to str, by using the output of str.c_str. Constructing a new std::string object in this case is unnecessary and inefficient. Because this code compiles and functions correctly, this inefficient code might not be noticed.

Fix

To fix this defect, eliminate calls to std::string functions that produce a C-sting. Use std::string instead. Choose appropriate function overloads when you use a string object instead of a C-string. Consider this code:

void set_prop1(const char* str);
void set_prop2(const std::string& str);
void foo( std::string& input){
	//...
	set_prop1(str.c_str()); // Necessary
	//...
	set_prop2(str); // Efficient	
}
Using str instead of str.c_str as input to set_prop2 makes the code more efficient and fixes the defect.

Performance improvements might vary based on the compiler, library implementation, and environment that you are using.

Examples

expand all

#include <string>
#include <utility>

class A
{
public:
	A( char const* );
	char const* c_str() const;
};
void CppLibFuncA(const std::string&);
void CppLibFuncB(std::string &&);
void bar( A const& );
std::string make_string();
bool contains( std::string const& str1, std::string const& str2 )
{
	return str1.find( str2.data() ) == std::string::npos;
}

void foo(const std::string& s, std::string&& rs, A& other){
	CppLibFuncA( s.data() ); 
	CppLibFuncB( std::move( rs ).c_str() ); 
	CppLibFuncA( make_string().data() ); 
	bar( other.c_str() ); 
	if(contains(s,make_string())){
		//...
	}
	
}

In this example, Polyspace® flags the implicit construction of a string object by using a C-string that is obtained from a string function.

  • The function CppLibFuncA takes a const std::string& as input. When the function CppLibFunc is called by using s.data(), the compiler cannot pass a reference to the object s to the function. Instead, the compiler implicitly constructs a new std::string object from the C-string pointer and passes the new object to the function,which is inefficient. Polyspace flags the call to std::string::data.

  • Because calling CppLibFuncB by using the output of std::string::c_str also implicitly constructs a new str::string object, Polyspace flags the call to std::string::c_str.

  • A call to the function bar is not flagged because a const char* is not implicitly converted to a new std::string object.

  • In the function contain, Polyspace flags the call to std::string::find(), where the output of std::string::data is used instead of an std::string object.

Correction

To fix this issue, avoid implicit construction of new std::string objects from the outputs of the std::string::c_str or std::string::data functions. Use the existing std::string objects instead.

#include <string>
#include <utility>
void CppLibFuncA(std::string const &);
void CppLibFuncB(std::string &&);
std::string make_string();
bool contains( std::string const& str1, std::string const& str2 )
{
	return str1.find( str2 ) == std::string::npos;
}
void foo(std::string const & s, std::string&& rs){
	CppLibFuncA( s ); 
	CppLibFuncB( std::move( rs ) ); 
	CppLibFuncA( make_string()); 
	if(contains(s,make_string())){
		//...
	}	
}

Fix calls to the functions CppLibFunc, CppLibFuncB, and CppLibFuncC by using the existing std::string objects as input.

#include <string>
#include <utility>
std::string make_string(void);
void bar(const std::string& s){
	std::string s1 = s.c_str(); // Inefficient
	std::string s2 = make_string();
	s2.append(s1.data());
}

In this example, Polyspace flags the explicit use of a C-string when:

  • The std::string object s is copied to s1 by calling s.c_str().

  • The std::string object s1 is appended calling s1.data().

.

Correction

To fix this issue, avoid using a C-string when you can use an std::string object.

#include <string>
#include <string>
#include <utility>
std::string make_string(void);
void bar(const std::string& s){
	std::string s1 = s; // Efficient
	std::string s2 = make_string();
	s2.append(s1);
}
#include <string>
#include <utility>
std::string make_string(void);
void bar2( std::string& s1){
	std::string s2 = make_string();
	s1.replace(1, 1, &s2[0]);               
	s1.replace(s1.begin(), s1.end(), &s2.at(0));   
	s1.append(s2.c_str());                          
	s1.assign(s2.data());                         
	s1.compare(s2.data());
	const char* p = s2.c_str();
	s1.find(p);                            
}

In this example, Polyspace flags the explicit use of a C-string to invoke member functions of the std::string class.

Correction

To fix this issue, avoid using C-strings when you can use an std::string object instead.

#include <string>
#include <string>
#include <utility>
std::string make_string(void);
void bar( std::string& s1){
	std::string s2 = make_string();
	s1.replace(1, 1, s2);               
	s1.replace(s1.begin(), s1.end(), s2);   
	s1.append(s2);                          
	s1.assign(s2);                         
	s1.compare(s2);                        
	s1.find(s2);                             
}
#include <string>
#include <utility>
void userDefined(const char* p){
	//...
}
void userDefined(const std::string& s){
	//...
	userDefined(s.c_str());//Compliant
	//userDefined(s);//Infinite recursion
}
void bar( const std::string& s){
	const char* p = s.data();    
	userDefined(p);
	userDefined(s.c_str());                         
}

In this example, the user-defined function userDefined is overloaded to accept either a const char* or a const std::string parameter. Polyspace flags the use of a C-string instead of an std::string object to call the function. Polyspace does not flag calls to std::string::c_str or std::string::data when they are used for calling the const char* overload of userDefined from the body of the std::string overload of userDefined. In this case, using std::string instead of a C-string results in an unintended infinite recursion.

Correction

To fix this issue, avoid using C-strings when you can use an std::string object instead.

#include <string>
#include <string>
#include <utility>
extern void userDefined(const char *);
extern void userDefined(const std::string &);
void bar( const std::string& s){                       
	userDefined(s);                     
}

Result Information

Group: Performance
Language: C++
Default: Off
Command-Line Syntax: EXPENSIVE_C_STR_STD_STRING_OPERATION
Impact: Medium

Version History

Introduced in R2020b

expand all