Pierre Johnson
Pierre Johnson

Reputation: 15

printf() confusion - printing (null) when trying to print string and printing correct value when printing buffer

see simplified code - I'm confused...

#include <stdio.h>
#include <string>      
#include <cstdlib>  // includes the standard library and overarchs over stdlib.h

using namespace std;  
void main()
{
    char buffer[10];
    string theString;
    int i = 997799; //(simplified)
    itoa(i,buffer,10);                      
    theString = buffer;             

    printf("\n string is: %s of length %d \n", theString, theString.length());
    printf("\n buffer is: %s of length %d \n", buffer, theString.length());
    return;
}

The Output I get is unexpected:

string is: (null) of length 926366009
buffer is: 997799 of length 6

(1) why is the string printing as null?

(2) why is theString.length() not printing properly in the first printf() but is right in the 2nd?

(3) if I set breakpoints in visual studio 'buffer' appears as "997799" while 'theString' appears as {"997799"} - something weird going on here?

Thanks folks! Edit I greatly appreciate the level of detail of the provided answers - they all added clarity and helped me go beyond my issue - many thanks for the time you spent helping out :)

Upvotes: 1

Views: 6556

Answers (4)

txtechhelp
txtechhelp

Reputation: 6777

As another answer pointed out, the reason for the (null) value is because the (formal) printf %s expects a const char* for its input, a std::string is not a const char* and results in undefined behavior... The reason for the random number when you do your first theString.length() has to do with your compiler.

On OpenBSD 5.1 with g++ 4.2.1, when I compile the code I get numerous warnings; one in particular is use of itoa, I had to change to sprintf, I also get the following warning about %s with printf on a std::string: warning: cannot pass objects of non-POD type 'struct std::string' through '...'; call will abort at runtime.

When I run the compiled code it aborts and core dumps at the first printf because of the call to %s on a std::string 'object' (which is the 'correct' behavior though technically still 'undefined behavior')

However, when I compile the above code (without edits) on an MS compiler, I actually get the following output:

string is: 997799 of length 6
buffer is: 997799 of length 6

Which is still 'undefined' but the 'expected' results.

So apparently the MS compiler recognizes the std::string on a printf and 'changes' it to a string.c_str() call OR the printf in the MS C/C++ library accepts a std::string and calls the .c_str() for you.

So to be 100% 'compatible' and C++ 'compliant' consider the following:

#include <iostream>
#include <sstream>

int main(int argc, char **argv)
{
    std::stringstream theString;
    int i = 997799; //(simplified)
    theString << i;

    std::cout << "string is: " << theString.str() << " of length " << theString.str().length() << std::endl;
    // printf("string is: %s of length %d \n", theString.str().c_str(), theString.str().length());
    return 0;
}

It's usually not good practice to mix C++ and C calls/styles, but IMHO I use printf a lot for it's 'convenience' but usually take it out or use some #defines for debug build use only.

I hope that can help answer the 'why for #2'

Upvotes: 1

Bill Lynch
Bill Lynch

Reputation: 81996

My compiler (clang on os x) reports the following errors:

c++     foo.cc   -o foo
foo.cc:6:1: error: 'main' must return 'int'
void main()
^~~~
int
foo.cc:11:5: error: use of undeclared identifier 'itoa'
    itoa(i,buffer,10);                      
    ^
foo.cc:14:48: error: cannot pass non-POD object of type 'string' (aka
      'basic_string<char, char_traits<char>, allocator<char> >') to variadic
      function; expected type from format string was 'char *'
      [-Wnon-pod-varargs]
    printf("\n string is: %s of length %d \n", theString, theString.length());
                          ~~                   ^~~~~~~~~
foo.cc:14:48: note: did you mean to call the c_str() method?
    printf("\n string is: %s of length %d \n", theString, theString.length());
                                               ^
                                                        .c_str()
foo.cc:14:59: warning: format specifies type 'int' but the argument has type
      'size_type' (aka 'unsigned long') [-Wformat]
    printf("\n string is: %s of length %d \n", theString, theString.length());
                                       ~~                 ^~~~~~~~~~~~~~~~~~
                                       %lu
foo.cc:15:56: warning: format specifies type 'int' but the argument has type
      'size_type' (aka 'unsigned long') [-Wformat]
    printf("\n buffer is: %s of length %d \n", buffer, theString.length());
                                       ~~              ^~~~~~~~~~~~~~~~~~
                                       %lu

Let's fix them:

  1. void main() { ... } should be int main() { ... }.

  2. itoa() is not standard in C++. There is std::stoi() though, so let's use that instead. We could also use std::snprintf if we are writing to a character array, and not a std::string.

  3. If we're going to use printf() with a std::string, we will need to use the std::string::c_str() method to return a character array and print that instead. printf itself doesn't understand C++ objects. (We could also use cout, which does understand c++ objects).

These changes cause the code to look like:

#include <cstdio>
#include <string>      
#include <cstdlib>

using namespace std;
int main()
{
    char buffer[10];
    string theString;
    int i = 997799; //(simplified)

    snprintf(buffer, sizeof(buffer), "%d", i);
    theString = buffer;

    printf("string is: %s of length %lu\n", theString.c_str(), theString.length());
    printf("buffer is: %s of length %lu\n", buffer, strlen(buffer));
}

And this code outputs:

[4:46pm][wlynch@watermelon /tmp] ./foo
string is: 997799 of length 6
buffer is: 997799 of length 6

Upvotes: 0

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153955

When you use the %s specifier with printf() you promise to pass a char const* as the corresponding argument. Passing anything except a char const* or something which decays into a char const* is undefined behavior. Certainly, passing a C++ object will have undefined behavior.

The proper way to pass a std::string to printf() is to use the %s format specifier and use the c_str() member, e.g.:

printf("string=%s\n", s.c_str());

You are using %d as a format specifier for std::string::size_type. That will probably work but it isn't guaranteed to work! Although std::string::size_type is guaranteed to be std::size_t, this type may be unsigned int, unsigned long, unsigned long long, or even some non-standard built-in integral type! The proper way to spell the format specifier for std::size_t is %zu (and certainly not %ul as in another post: it was probably meant to be %lu which is, however, still wrong:

printf("string.size()=%zu\n", s.size());

Since you are using C++, you are probably better off having the compiler figure out what formatting to call:

std::cout << "\n string is: " << theString << " of length " << theString.length() << " \n";
std::cout << "\n buffer is: " << buffer << " of length " << theString.length() << " \n";

Upvotes: 5

Vlad from Moscow
Vlad from Moscow

Reputation: 311068

The program has undefined behaviour because function printf is unable to output objects of type std::string. When format symbol %s is used the function supposes that the corresponding argument is a pointer of a string literal. So the function tries to output the object of type std::string as a string literal. To use correctly function printf with objects of type std::string you should convert them to strings using member function c_str() or data() (for C++ 2011). For example

printf("\n string is: %s of length %ul \n", theString.c_str(), theString.length());

Upvotes: 3

Related Questions