C++ printf %p 说明符在 Linux 和 Windows 中的行为不同

Posted

技术标签:

【中文标题】C++ printf %p 说明符在 Linux 和 Windows 中的行为不同【英文标题】:C++ printf %p specifier behaves differently in Linux and Windows 【发布时间】:2017-07-19 19:03:04 【问题描述】:

我有一个 C++ 代码,可以在 Linux 和 Windows 上运行。

部分代码包括使用vsnprintf 以使用格式字符串和参数获取字符串。

我注意到,每当格式字符串包含 %p 时,Linux 和 Windows 上的结果是不同的 - Windows 不会在结果前面加上 0x 而 linux 会这样做,而且 Windows 使用大写的地址字母而 Linux 使用小写.

我找不到使两个版本相同的标志。

我的偏好是让 Linux 版本的行为类似于 Windows 版本(因为 Windows 代码是原始代码,所以这就是程序预期的行为方式)。

如果无法在 Linux 中更改 vsnprintf 的行为,我想要一种方法来“修复”在 vsnprintf 输出后包含 %p 的字符串(以一种有效的方式)。

【问题讨论】:

%p 的行为是implementation defined @Borgleader:不是。 0x%X 需要 unsigned int 类型的参数;给它一个指针具有未定义的行为。如果unsigned int 和指针大小相同(它们通常不是),它可能会起作用;这始终是未定义行为的可能结果。 您的问题被标记为 C 和 C++。它们是两种不同的语言。你用的是哪一个? (在极少数情况下,您可能希望编写在两种语言中都能正常工作的代码;如果是这样,请在问题中说明。) @KeithThompson 我已经更新了这个问题。它在 C++ 中 @Justin 没有说明如何将指针转换为uintptr_t。只有与这些之间的转换才能保证工作。 【参考方案1】:

%p 打印的字符串是implementation defined。这就是 Linux 和 Windows 上的行为不同的原因。如果您想要一致的行为,则必须实现自己的版本。

使用uintptr_t,我们可以得到一个可以容纳指针的整数。所以我们可以将reinterpret_cast 指针插入其中。请注意,虽然转换会成功,但没有指定值将保持什么。

然后,我们可以使用std::hex 或适当的format macro constant 将整数打印为十六进制:

auto* myPointer = ...;
std::cout << std::hex << reinterpret_cast<std::uintptr_t>(myPointer) << '\n';
std::printf("%" PRIxPTR "\n", reinterpret_cast<std::uintptr_t>(myPointer));

Demo

具体格式由您决定。

【讨论】:

我想尝试这个解决方案,但我需要能够在运行时以编程方式检测格式字符串中%p 的每次出现,并将其替换为PRIXPTR。我怎样才能做到这一点? (检测很容易,但我如何用宏替换字符串p @darkThoughts 请注意,对于字符串文字,格式宏常量是 #defines。比如在coliru上,PRIxPTR"lx"是一样的(当然不同平台实际值会有所不同)。所以你可以找到"%p" 的出现并用"%" PRIxPTR 替换它们(在coliru 上,它相当于"%lx" 但由于定义依赖于平台,我不能只用"%X""%llX" 静态替换出现的"%p" - 我必须用定义替换它们。我该怎么做? @darkThoughts "some string with %p in the middle" -> "some string with %" PRIXPTR " in the middle" 我该如何做这个替换 - 我需要在 p 周围拆分原始字符串吗?【参考方案2】:

正如Justin's answer 建议的那样,您可以将指针转换为uintptr_t 并根据需要对其进行格式化。这可能是 99% 的可移植性——而且由于您可能只关心 Linux 和 Windows,这可能已经足够好了。 (在没有足够大的整数类型来保存指针值而不丢失信息的系统上,它可能会失败。这样的系统不会定义uintptr_t。你不太可能遇到这样的系统。)

另一种方法是使用%p 格式将指针格式化为字符串,然后操作生成的字符串以获得所需的一致结果。创建一个接受void* 参数并返回std::string 的函数可能是有意义的。

这是我的尝试(我没有声称这是好的 C++ 代码)。它会删除前导 0x0X(如果存在),并将所有剩余字符映射为大写。

如果您没有 C++11 编译器,请调整 for 循环。

#include <iostream>
#include <string>
#include <cstdio>
#include <cctype>

std::string hex(void* ptr) 
    const int big_enough = 100;
    char s[big_enough];
    std::snprintf(s, sizeof s, "%p", ptr);
    std::string result = s;
    std::string prefix = result.substr(0, 2);
    if (prefix == "0x" || prefix == "0X") 
        result = result.substr(2);
    
    for (auto &&c : result) 
        c = std::toupper((unsigned char)c);
    
    return result;


int main() 
    int n;
    int *ptr = &n;
    std::printf("Using %%p:  %p\n", (void*)ptr);
    std::cout << "Using hex(): " << hex((void*)ptr) << "\n";

我的 Linux 系统上的输出是:

Using %p:  0x7ffd6e8ca884
Using hex(): 7FFD6E8CA884

【讨论】:

以上是关于C++ printf %p 说明符在 Linux 和 Windows 中的行为不同的主要内容,如果未能解决你的问题,请参考以下文章

C++笔试强训第六天

c++(vs上)与g++(linux下)对于++操作的汇编代码解读

_Bool 的 printf 转换说明符?

printf 格式化输出符号详细说明

Linux 命令(225)—— printf 命令

Linux 命令(225)—— printf 命令