开发者

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...


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.


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.


You cannot insert Non-POD data into variadic functions. More info


if the variadic-function call is calling operator (const char*) on it, why wouldn't it do so for my own class?

Yes but you should explicitly cast it in your code: printf("%s", (LPCSTR)s, ...);.


It doesn't. It doesn't even call the operator const char*. Visual C++ just passes the class data to printf as if by memcpy. It works because of the layout of the CString class: It only contains one member variable which is a pointer to the character data.


If the variadic-function call is translated into pushing the pointers of the arguments, …

That is not how variadic functions work. The values of the arguments, rather than pointers to the arguments, are passed, after special conversion rules for built-in types (such as char to int).

C++03 §5.2.2p7:

When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.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. If the argument has integral or enumeration type that is subject to the integral promotions (4.5), or a floating point type that is subject to the floating point promotion (4.6), the value of the argument is converted to the promoted type before the call. These promotions are referred to as the default argument promotions.

In particular from the above:

If the argument has a non-POD class type (clause 9), the behavior is undefined.

C++ punts to C for the definition of va_arg, and C99 TC3 §7.15.1.2p2 says:

… if type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined, except for the following cases: [list of cases that don't apply here]

Thus, if you pass a class type, it must be POD, and the receiving function must apply the correct type, otherwise the behavior is undefined. This means that in the worst case, it may work exactly as you expect.

Printf will not apply the correct type for any user-defined class type as it has no knowledge of them, so you cannot pass any UDT class type to printf. Your foo does the same thing by using a char pointer instead of the correct class type.


Your printf statement is wrong:

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

Should be:

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

Which will print out "aha my".

CString has a converstion operator for const char* (its actually for LPCTSTR which is a const TCHAR* - CStringA has a conversion function for LPCSTR).

The printf call will not convert your CStringA object to a CStringA* pointer. It essentially treats it like a void*. In the case of CString, it is sheer luck (or perhaps design of Microsoft's developers taking advantage of something that isn't in the standard) that it will give you the NULL-terminated string. If you were to use a _bstr_t instead (which has the size of the string first), despite having the conversion function, it would fail horribly.

It is good practice (and required in many cases) to explicitly cast your objects/pointers to what you want them to be when you call printf (or any variadic function for that matter).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜