printf如何知道CString的字符数据的地址?

时间:2011-02-04 13:43:43

标签: c++ type-conversion printf variadic-functions

考虑此代码片段:

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 );

此代码段会产生“直观”输出“aha”。但我不知道这是如何工作的:

  • 如果将variadic-function调用转换为推送参数的指针,printf将收到一个被解释为CStringA*的{​​{1}}
  • 如果variadic-function调用正在调用const char*,为什么不能为我自己的类调用呢?

有人可以解释一下吗?

编辑:添加了一个虚拟的可变参数函数,将其参数视为operator (const char*) s。看哪 - 当它到达const char*论点时它甚至会崩溃......

7 个答案:

答案 0 :(得分:7)

C ++ 98标准§5.2.2/ 7的相关文本:

  

对参数表达式执行左值到右值(4.1),数组到指针(4.2)和函数到指针(4.3)标准转换。在这些转换之后,如果参数没有算术,枚举,指针,成员指针或类类型,则程序格式错误。如果参数具有非POD类类型(第9节),则行为未定义。

所以正式行为未定义

但是,给定的编译器可以提供任意数量的语言扩展,而Visual C ++则可以。对于将参数传递给...MSDN Library记录了Visual C ++的行为,如下所示:

  • 如果实际参数的类型为float,则在函数调用之前将其提升为double类型。
  • 使用整数提升将任何有符号或无符号的char,short,枚举类型或位字段转换为signed或unsigned int。
  • 类类型的任何参数都通过值作为数据结构传递;副本是通过二进制复制而不是通过调用类的复制构造函数(如果存在)来创建的。

这里没有提到任何关于应用用户定义转换的Visual C ++。

可能是CString::Format,这是你真正感兴趣的功能,取决于上面的最后一点。

干杯&amp;第h。,

答案 1 :(得分:6)

您正在做的是未定义的行为,并且是您的编译器提供的非标准扩展或纯粹的运气。我猜测CString将字符串数据存储为结构中的第一个元素,因此从CString读取就像它是char *一样,产生一个有效的以空字符结尾的字符串。

答案 2 :(得分:4)

您无法将非POD数据插入到可变参数函数中。 More info

答案 3 :(得分:1)

  

如果variadic-function调用在它上面调用operator(const char *),为什么它不能为我自己的类做呢?

是的,但您应该在代码中明确地投射它:printf("%s", (LPCSTR)s, ...);

答案 4 :(得分:1)

  

如果将variadic-function调用转换为推送参数的指针,......

这不是可变函数的工作原理。在内置类型的特殊转换规则(例如char到int)之后,传递参数的而不是参数的指针。

C ++03§5.2.2p7:

  

当给定参数没有参数时,参数的传递方式是接收函数可以通过调用va_arg(18.7)来获取参数的值。在参数表达式上执行左值到右值(4.1),数组到指针(4.2)和函数到指针(4.3)标准转换。在这些转换之后,如果参数没有算术,枚举,指针,成员指针或类类型,则程序格式错误。如果参数具有非POD类类型(第9节),则行为未定义。如果参数具有由积分促销(4.5)或浮点促销(4.6)限制的浮点类型的积分或枚举类型,则参数的值将在调用之前转换为提升类型。这些促销被称为默认参数促销。

特别是从上面的内容:

  

如果参数具有非POD类类型(第9节),则行为未定义。

C ++用于定义va_arg,C99TC3§7.15.1.2p2说:

  

...如果type与实际的下一个参数的类型不兼容(根据默认参数提升而提升),则行为是未定义的,除了以下情况:[此处不适用的案例列表]

因此,如果传递类类型,它必须是POD,并且接收函数必须应用正确的类型,否则行为是未定义的。这意味着在最坏的情况下,它可能完全按照您的预期工作。

Printf不会为任何用户定义的类类型应用正确的类型,因为它不知道它们,因此您不能将任何UDT类类型传递给printf。你的foo通过使用char指针而不是正确的类类型来做同样的事情。

答案 5 :(得分:1)

没有。它甚至不会调用operator const char*。 Visual C ++只是将类数据传递给printf,就像memcpy一样。它的工作原因是CString类的布局:它只包含一个成员变量,它是指向字符数据的指针。

答案 6 :(得分:0)

您的printf声明错误:

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

应该是:

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

将打印出“啊哈我的”。

CString有const char*的转化运算符(实际上LPCTSTRconst TCHAR* - CStringA具有LPCSTR的转换函数。< / p>

printf调用不会将您的CStringA对象转换为CStringA*指针。它基本上将其视为void*。在CString的情况下,它是纯粹的运气(或者可能是微软的开发人员利用不符合标准的东西的设计),它将为您提供以NULL结尾的字符串。如果您使用_bstr_t代替(首先使用字符串的大小),尽管具有转换功能,但它会失败。

在调用printf(或任何可变函数)时,明确地将对象/指针强制转换为您想要的对象是一种很好的做法(在很多情况下都是必需的)。