如何在C语言中打印变量的内存内容?

29
假设我执行以下操作:

double d = 234.5;

我想查看 d 的内存内容 [全部8个字节]

我该怎么做?


你想以什么形式查看它? - danben
8
为什么您认为它是八个字节? - Martin York
3
我认为浮点数通常是四个字节,双精度浮点数通常是八个字节。 - Dolph
@Martin:难道不是八个字节吗? - Lazer
3
这是实现定义的。 - user1831086
5
double类型的大小取决于硬件和编译器选项。 - Martin York
9个回答

31
unsigned char *p = (unsigned char *)&d;
int i;

for (i = 0; i < sizeof d; i++)
    printf("%02x ", p[i]);

3
@Craig说:"sizeof d"是正确的,我不知道为什么你改成了"sizeof(d)"(这样也可以,但许多人在"d"不是类型时喜欢使用"sizeof d")。 - Alok Singhal
4
同意 - 我更喜欢明确它是一个操作符,而不是一个函数(我也不会使用return(x) - caf
4
最好总是使用括号,以便不必在大脑中存储更多的东西 :-) 我更喜欢使用变量而不是类型,以防变量的类型发生改变时,我不必修改太多的代码。 - paxdiablo

21
double d = 234.5;

/* 1. use a union */
union u {
    double d;
    unsigned char c[sizeof(double)];
};
union u tmp;
size_t i;
tmp.d = d;
for (i=0; i < sizeof(double); ++i)
    printf("%02x\n", tmp.c[i]);

/* 2. memcpy */
unsigned char data[sizeof d];
size_t i;
memcpy(data, &d, sizeof d);
for (i=0; i < sizeof d; ++i)
    printf("%02x\n", data[i]);

/* 3. Use a pointer to an unsigned char to examine the bytes */
unsigned char *p = (unsigned char *)&d;
size_t i;
for (i=0; i < sizeof d; ++i)
    printf("%02x\n", p[i]);

所有的方法都会展示给你字节码—但是同一个double值在不同的系统上可能会以不同的方式打印字节,例如由于不同的编码(罕见情况)或不同的大小端。


我个人会使用指针,但是在我看来,联合体更好。不错。我经常忘记它。 - Xorlev
Xorlev:从技术上讲,联合方法并不保证可行,而其他两种方法是可以的。 - caf
@caf:为什么你说联合方法不能保证可行?只要在联合中使用unsigned char数组,它就应该可以工作。 - Alok Singhal
C标准的附录J将“除最后一个存储的联合成员之外的值”列为“未指定”,这正是我所考虑的。然而,我不认为该附录是规范性的,并且我找不到一个规范性的参考文献来证明同样的事情,因此该附录可能存在错误。 - caf
@caf,谢谢。脚注82说:“如果用于访问联合对象内容的成员与最后用于在对象中存储值的成员不同,则该值的对象表示的适当部分将被重新解释为新类型的对象表示,如6.2.6所述(有时称为“类型游戏”)。这可能是一个陷阱表示。但这也是非规范的。”从我阅读comp.lang.c上的许多留言来看,似乎人们认为这种特定方法(union中的unsigned char)是被允许的。 - Alok Singhal

9

感谢我有用的代码片段库,这里提供了一个完整的C语言解决方案,包括测试工具,并提供十六进制和ASCII数据:

#include <stdio.h>

void hexDump (char *desc, void *addr, int len) {
    int i;
    unsigned char buff[17];       // stores the ASCII data
    unsigned char *pc = addr;     // cast to make the code cleaner.

    // Output description if given.

    if (desc != NULL)
        printf ("%s:\n", desc);

    // Process every byte in the data.

    for (i = 0; i < len; i++) {
        // Multiple of 16 means new line (with line offset).

        if ((i % 16) == 0) {
            // Just don't print ASCII for the zeroth line.

            if (i != 0)
                printf ("  %s\n", buff);

            // Output the offset.

            printf ("  %04x ", i);
        }

        // Now the hex code for the specific character.

        printf (" %02x", pc[i]);

        // And store a printable ASCII character for later.

        if ((pc[i] < 0x20) || (pc[i] > 0x7e))
            buff[i % 16] = '.';
        else
            buff[i % 16] = pc[i];
        buff[(i % 16) + 1] = '\0';
    }

    // Pad out last line if not exactly 16 characters.

    while ((i % 16) != 0) {
        printf ("   ");
        i++;
    }

    // And print the final ASCII bit.

    printf ("  %s\n", buff);
}

int main (int argc, char *argv[]) {
    double d1 = 234.5;
    char s1[] = "a 15char string";
    char s2[] = "This is a slightly longer string";
    hexDump ("d1", &d1, sizeof d1);
    hexDump ("s1", &s1, sizeof s1);
    hexDump ("s2", &s2, sizeof s2);
    return 0;
}

我的系统输出结果是:
d1:
  0000  00 00 00 00 00 50 6d 40                          .....Pm@
s1:
  0000  61 20 31 35 63 68 61 72 20 73 74 72 69 6e 67 00  a 15char string.
s2:
  0000  54 68 69 73 20 69 73 20 61 20 73 6c 69 67 68 74  This is a slight
  0010  6c 79 20 6c 6f 6e 67 65 72 20 73 74 72 69 6e 67  ly longer string
  0020  00                                               .

由于此问题也带有 C++ 标签,这里提供一个 I/O 流版本作为比较。即使你不是 iostream 的特别粉丝,如果你已经在使用它们,它仍然适用。能够使用 hexdump(any_obj) 也很好,但当然可以通过类似构造函数的委托函数模板来实现。
#include <iomanip>
#include <ostream>
#include <string>

struct hexdump {
  void const* data;
  int len;

  hexdump(void const* data, int len) : data(data), len(len) {}

  template<class T>
  hexdump(T const& v) : data(&v), len(sizeof v) {}

  friend
  std::ostream& operator<<(std::ostream& s, hexdump const& v) {
    // don't change formatting for s
    std::ostream out (s.rdbuf());
    out << std::hex << std::setfill('0');

    unsigned char const* pc = reinterpret_cast<unsigned char const*>(v.data);

    std::string buf;
    buf.reserve(17); // premature optimization

    int i;
    for (i = 0; i < v.len; ++i, ++pc) {
      if ((i % 16) == 0) {
        if (i) {
          out << "  " << buf << '\n';
          buf.clear();
        }
        out << "  " << std::setw(4) << i << ' ';
      }

      out << ' ' << std::setw(2) << unsigned(*pc);
      buf += (0x20 <= *pc && *pc <= 0x7e) ? *pc : '.';
    }
    if (i % 16) {
      char const* spaces16x3 = "                                                ";
      out << &spaces16x3[3 * (i % 16)];
    }
    out << "  " << buf << '\n';

    return s;
  }
};

int main() {
  std::cout << "double:\n" << hexdump(234.5);
  std::cout << "string 1:\n" << hexdump("a 15char string");
  std::cout << "string 2:\n" << hexdump("This is a slightly longer string");

  return 0;
}

由于此问题也标记了C ++,因此可以比较iostream版本:http://codepad.org/trVz9mlQ。我不是iostreams的特别粉丝,但如果您已经在使用它们,则它很适合。能够使用`hexdump(any_obj)`也很好(但当然可以使用类似于该构造函数的委托函数模板来完成)。 - Roger Pate
@Roger,我本来想把你的代码加入到我的回答中(当然要注明出处,并建议如果你发布自己的答案,我会从我的回答中删除它)。这是因为即使其他网站(如codepad)消失了,我仍然希望SO保持有用。然而,看到你在SO用户页面上的版权政策让我停下了脚步——如果你保留版权,没有人可以使用你的代码,除了教育之外毫无用处。因此,我更希望你将其发布为一个正式的答案,而不是一个带链接的评论,但完全由你决定。 - paxdiablo
我认为这与您的回答更相关,因此不值得分开回复,并且评论空间有限,所以我只是本能地使用了codepad。我将编辑问题以包含它,然后我将成为将其放置在SO许可下的人。您可以根据需要重新编辑。顺便说一句,虽然我认为您现在已经意识到了这一点,但是我的个人简介中提到在其他地方发布的代码不受SO许可证保护,但我原本打算是更多地关于较长,不太琐碎的代码,而不是codepad.org的短粘贴代码。 - Roger Pate

2

尝试

union Plop
{
    double   value;
    char     data[sizeof(double)];
};

Plop print;
print.value = 234.5;

std::copy(print.data,print.data+sizeof(double),std::ostream_iterator<int>(std::cout)," ");
std::cout << std::endl;

除非我弄错了,通过两个不同的成员访问联合会导致未定义的行为。不过,我认为只有当新成员具有陷阱值时才会出现问题,但是 char 没有这种情况。 - GManNickG
1
@GMan:标准对于联合中的无符号字符或无符号字符数组做了一个例外。 - Alok Singhal
@Martin:我认为标准允许char有填充,但unsigned char不能有填充。换句话说,在你上面的代码中,data[i]可能不是一个有效的char值。我现在找不到相关章节的标准,但我相当确定它存在。 :-) - Alok Singhal
@Alok:“对于字符类型[目前包括所有3种类型],对象表示的所有位都参与值表示。”(3.9.1/1)没有填充位。 - Roger Pate
@Roger,抱歉,当我说85而不是82时,我可能患有阅读障碍。基本上,我想说n1256在访问联合成员时,除了最后一个修改的成员外,会将其重定向到6.2.6,但正如你所说,n1124没有这个引用。此时,我不知道在这种情况下实际上保证了什么:-)。 - Alok Singhal
显示剩余8条评论

2
如果您想以位的形式打印双精度值,请尝试以下方法。我已经为float值尝试过了。如果您更改了它,就可以查看64位双精度值。
#include <stdio.h>

int main (void)
{
        float f = 10.0f;

        struct Float {
                unsigned char bit01:1;
                unsigned char bit02:1;
                unsigned char bit03:1;
                unsigned char bit04:1;
                unsigned char bit05:1;
                unsigned char bit06:1;
                unsigned char bit07:1;
                unsigned char bit08:1;
                unsigned char bit09:1;
                unsigned char bit10:1;
                unsigned char bit11:1;
                unsigned char bit12:1;
                unsigned char bit13:1;
                unsigned char bit14:1;
                unsigned char bit15:1;
                unsigned char bit16:1;
                unsigned char bit17:1;
                unsigned char bit18:1;
                unsigned char bit19:1;
                unsigned char bit20:1;
                unsigned char bit21:1;
                unsigned char bit22:1;
                unsigned char bit23:1;
                unsigned char bit24:1;
                unsigned char bit25:1;
                unsigned char bit26:1;
                unsigned char bit27:1;
                unsigned char bit28:1;
                unsigned char bit29:1;
                unsigned char bit30:1;
                unsigned char bit31:1;
                unsigned char bit32:1;
        };

        struct Float *F;

        F = (struct Float *) &f;

        printf("\nMSB -->1 bit for sign bit; 8 bit for exponent; 23 bit for mantisa<-- LSB\n");
        printf("%d ", F->bit32);
        printf("%d", F->bit31);
        printf("%d", F->bit30);
        printf("%d", F->bit29);
        printf("%d", F->bit28);
        printf("%d", F->bit27);
        printf("%d", F->bit26);
        printf("%d", F->bit25);
        printf("%d ", F->bit24);
        printf("%d", F->bit23);
        printf("%d", F->bit22);
        printf("%d", F->bit21);
        printf("%d", F->bit20);
        printf("%d", F->bit19);
        printf("%d", F->bit18);
        printf("%d", F->bit17);
        printf("%d", F->bit16);
        printf("%d", F->bit15);
        printf("%d", F->bit14);
        printf("%d", F->bit13);
        printf("%d", F->bit12);
        printf("%d", F->bit11);
        printf("%d", F->bit10);
        printf("%d", F->bit09);
        printf("%d", F->bit08);
        printf("%d", F->bit07);
        printf("%d", F->bit06);
        printf("%d", F->bit05);
        printf("%d", F->bit04);
        printf("%d", F->bit03);
        printf("%d", F->bit02);
        printf("%d\n", F->bit01);
}

2
如果您想从gdb中查看此内容,可以输入以下命令:

如果你想从gdb中查看这个,你可以输入:

x /gx d

1
你尝试过获取 d 的地址,然后从该地址开始打印 sizeof(d) 字节吗?

1

使用友好的调试器是查看内存位置的值最佳方式,如果您只是想查看的话。


0

我认为你可以使用移位操作和掩码来“屏蔽”实际的位。

int t = 128;

for(int i=0;i<8;++i) { printf("%d", p & t);

p =>> 1;

t =>> 1; }


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接