Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how does printf know the address of a CString's character data?

Considering this code fragment:

struct My {
  operator const char*()const{ return "my"; }
} my;

CStringA s( "aha" );
printf("%s %s", s, my );


// another variadic function to get rid of comments about printf :)
void foo( int i, ... ) {
  va_list vars;
  va_start(vars, i);
  for( const char* p = va_arg(vars,const char*)
     ; p != NULL
     ; p=va_arg(vars,const char*) ) 
  {
    std::cout << p << std::endl;
  }
  va_end(vars);
}
foo( 1, s, my );

This snippet results in the 'intuitive' output "aha". But I haven't got a clue how this can work:

  • if the variadic-function call is translated into pushing the pointers of the arguments, printf will receive a CStringA* that is interpreted as a const char*
  • if the variadic-function call is calling operator (const char*) on it, why wouldn't it do so for my own class?

Can someone explain this?

EDIT: added a dummy variadic function that treats it's arguments as const char*s. Behold - it even crashes when it reaches the my argument...

like image 567
xtofl Avatar asked Feb 04 '11 13:02

xtofl


2 Answers

The relevant text of C++98 standard §5.2.2/7:

The lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the argument expression. After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If the argument has a non-POD class type (clause 9), the behavior is undefined.

So formally the behavior is undefined.

However, a given compiler can provide any number of language extensions, and Visual C++ does. The MSDN Library documents the behavior of Visual C++ as follows, with respect to passing arguments to ...:

  • If the actual argument is of type float, it is promoted to type double prior to the function call.
  • Any signed or unsigned char, short, enumerated type, or bit field is converted to either a signed or an unsigned int using integral promotion.
  • Any argument of class type is passed by value as a data structure; the copy is created by binary copying instead of by invoking the class's copy constructor (if one exists).

This doesn’t mention anything about Visual C++ applying user defined conversions.

MS CString is "cleverly" layed out, so that it's POD representation is exactly the pointer to its null terminated character string. (sizeof(CStringA) == sizeof(char*)) When it is used in any printf-style function the function just get's passed the character pointer.

So this works because of the last point above and the way CString is layed out.

like image 110
Cheers and hth. - Alf Avatar answered Sep 23 '22 00:09

Cheers and hth. - Alf


What you're doing is undefined behaviour, and is either a non-standard extension provided by your compiler or works by sheer luck. I'm guessing that the CString stores the string data as the first element in the structure, and thus that reading from the CString as if it were a char * yields a valid null-terminated string.

like image 34
Tim Martin Avatar answered Sep 21 '22 00:09

Tim Martin