可变参数列表如何与 C++ 中的重载对象一起使用?
How does variable argument list work with overloaded objects in C++?
将变量参数传递给语句时,不会传递类型信息。
假设此示例使用字符串对象(我使用的是 MFC/ATL 的 CString)
CString name = "kevin";
printf("Hi %s", name);
printf 语句(或 CString)如何知道 grab/return LPCSTR 运算符并因此获得指向字符串的指针,而不是指向对象的指针?
我正在尝试编写自己的字符串class(它需要在一个巨大的项目中替换CString),但是当我执行上面的printf时,它最终会出现乱码,因为它没有得到名称的正确值。
How does the printf statement (or the CString) know to grab/return the LPCSTR operator and thus get a pointer to the string, and not a pointer to the object?
没有。 printf
不是类型安全的。它会盲目地假装 name
是 C 字符串而不是 C++ class 对象。这是未定义的行为,允许程序做任何它想做的事情,包括按预期工作。
要使其正常工作,您要么必须显式转换对象,要么使用类型安全的东西,这与 printf
不同。
如果调用似乎正常工作,CString
的第一个或唯一数据成员可能是适当类型的 C 字符串,其地址与对象的地址相同(类似于 this example).但是,永远不应依赖此行为。
它最终变成乱码的原因是因为 C 风格的可变参数传递没有任何类型转换。您将一个可能具有许多字段和任意布局的对象传递给一个函数,该函数在本质上与将其转换为 void*
做同样的事情——即它会删除类型信息。如果你想让你的代码工作,你需要将它转换为你希望函数看到的类型。在这种情况下,你需要写:
printf("Hi %s", (LPCSTR)name);
这样,实际传递给 printf 函数的值是指向基础字符串的指针,这是 %s
格式说明符所期望的。
真正的答案与对运算符 LPCTSTR () 的隐式调用无关,我认为它的实际工作方式有点聪明。
要实现一个字符串class,你需要字符串的数据缓冲区,当然,还有一些其他的数据字段,比如字符串的长度,可能是分配了多少内存,可能是一个参考计数等
但是如何让可变参数函数在传递 CString 参数时看到 C 风格的字符串?您可以将数据缓冲区字段(指向 char* 的指针)作为第一个成员,但随后所有其他字段(如字符串长度)也会意外地被压入堆栈,就好像它们是额外的函数参数一样。
因此,您接下来要尝试的是将所有字段打包到一个内存结构中,该内存结构与字符串的字符数据缓冲区一起分配。如果此数据缓冲区位于聚合结构的开头,则需要 C 字符串的可变参数函数将看到此结构的开头。如果终端 null 被放置在数据缓冲区的末尾,在其他数据字段之前,那么 printf 将在它遇到您不希望它看到的数据之前停止读取字符串数据,并且一切正常!
但是你意识到你有一个新问题——字符数据缓冲区的长度是可变的,"length"字段现在位于字符数据之后的内存中,所以你需要知道"length" 为了知道在哪里可以找到 "length" 字段。
那么你能否将"length"和所有其他额外字段移动到结构的开头,在内存中字符数据之前?当然不是,printf 现在会看到在真实字符串的开头添加了一些有趣的字符(甚至是空字符)。
但这就是 CString 所做的。但是,它的单个成员变量是一个指针,不是指向聚合结构的开头,而是指向字符数据开始的内存位置。
并且因为数据字段(没有字符缓冲区)具有固定大小,字符串 class 知道如何在内存中访问它们,给定它指向 char 的指针(这些字段位于负偏移处从指向 char 的指针)。当然,当它释放聚合结构时,class 还需要计算其原始内存分配开始的位置,而不仅仅是使用其指向 char 的指针。
将变量参数传递给语句时,不会传递类型信息。
假设此示例使用字符串对象(我使用的是 MFC/ATL 的 CString)
CString name = "kevin";
printf("Hi %s", name);
printf 语句(或 CString)如何知道 grab/return LPCSTR 运算符并因此获得指向字符串的指针,而不是指向对象的指针?
我正在尝试编写自己的字符串class(它需要在一个巨大的项目中替换CString),但是当我执行上面的printf时,它最终会出现乱码,因为它没有得到名称的正确值。
How does the printf statement (or the CString) know to grab/return the LPCSTR operator and thus get a pointer to the string, and not a pointer to the object?
没有。 printf
不是类型安全的。它会盲目地假装 name
是 C 字符串而不是 C++ class 对象。这是未定义的行为,允许程序做任何它想做的事情,包括按预期工作。
要使其正常工作,您要么必须显式转换对象,要么使用类型安全的东西,这与 printf
不同。
如果调用似乎正常工作,CString
的第一个或唯一数据成员可能是适当类型的 C 字符串,其地址与对象的地址相同(类似于 this example).但是,永远不应依赖此行为。
它最终变成乱码的原因是因为 C 风格的可变参数传递没有任何类型转换。您将一个可能具有许多字段和任意布局的对象传递给一个函数,该函数在本质上与将其转换为 void*
做同样的事情——即它会删除类型信息。如果你想让你的代码工作,你需要将它转换为你希望函数看到的类型。在这种情况下,你需要写:
printf("Hi %s", (LPCSTR)name);
这样,实际传递给 printf 函数的值是指向基础字符串的指针,这是 %s
格式说明符所期望的。
真正的答案与对运算符 LPCTSTR () 的隐式调用无关,我认为它的实际工作方式有点聪明。
要实现一个字符串class,你需要字符串的数据缓冲区,当然,还有一些其他的数据字段,比如字符串的长度,可能是分配了多少内存,可能是一个参考计数等
但是如何让可变参数函数在传递 CString 参数时看到 C 风格的字符串?您可以将数据缓冲区字段(指向 char* 的指针)作为第一个成员,但随后所有其他字段(如字符串长度)也会意外地被压入堆栈,就好像它们是额外的函数参数一样。
因此,您接下来要尝试的是将所有字段打包到一个内存结构中,该内存结构与字符串的字符数据缓冲区一起分配。如果此数据缓冲区位于聚合结构的开头,则需要 C 字符串的可变参数函数将看到此结构的开头。如果终端 null 被放置在数据缓冲区的末尾,在其他数据字段之前,那么 printf 将在它遇到您不希望它看到的数据之前停止读取字符串数据,并且一切正常!
但是你意识到你有一个新问题——字符数据缓冲区的长度是可变的,"length"字段现在位于字符数据之后的内存中,所以你需要知道"length" 为了知道在哪里可以找到 "length" 字段。
那么你能否将"length"和所有其他额外字段移动到结构的开头,在内存中字符数据之前?当然不是,printf 现在会看到在真实字符串的开头添加了一些有趣的字符(甚至是空字符)。
但这就是 CString 所做的。但是,它的单个成员变量是一个指针,不是指向聚合结构的开头,而是指向字符数据开始的内存位置。
并且因为数据字段(没有字符缓冲区)具有固定大小,字符串 class 知道如何在内存中访问它们,给定它指向 char 的指针(这些字段位于负偏移处从指向 char 的指针)。当然,当它释放聚合结构时,class 还需要计算其原始内存分配开始的位置,而不仅仅是使用其指向 char 的指针。