代码之家  ›  专栏  ›  技术社区  ›  asdfadf

为什么具有相同内存值的字符串和整数打印方式不同?

  •  3
  • asdfadf  · 技术社区  · 7 月前

    我正在前进 艰难地学习C ,其中作者经常要求学生有意识地“打破”例子来学习调试。 在练习11中,建议尝试将字符串打印为整数。很简单,但在下面的代码中,为什么不 name 输出与 number 当两者都以整数形式打印时?

    我的代码,简化:

    #include <stdio.h>
    
    int main() {
       // This should be equal to 5318008, according to my terrible Python code.
       char name[4] = {'x', '%', 'Q', 0};
       int number = 5318008;
       printf("%d\n", name);
       printf("%d\n", number);
       return 0;
    }
    

    运行时的输出:

    % ./temp
    1962793460
    5318008
    

    gdb的输出,显示这些变量在内存中是相同的(据我有限的经验所知):

    % gdb --quiet ./temp
    Reading symbols from ./temp...
    (gdb) break 7
    Breakpoint 1 at 0x116e: file ./temp.c, line 7.
    (gdb) run
    Starting program: /home/asdfadf/Documents/c/temp
    
    This GDB supports auto-downloading debuginfo from the following URLs:
      <https://debuginfod.archlinux.org>
    Enable debuginfod for this session? (y or [n])
    Debuginfod has been disabled.
    To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/usr/lib/libthread_db.so.1".
    
    Breakpoint 1, main () at ./temp.c:7
    7          printf("%d\n", name);
    (gdb) x/4xb &name
    0x7fffffffd9e4: 0x78    0x25    0x51    0x00
    (gdb) x/4xb &number
    0x7fffffffd9e0: 0x78    0x25    0x51    0x00
    (gdb)
    
    4 回复  |  直到 7 月前
        1
  •  4
  •   Eric Postpischil    7 月前

    printf("%d\n", name); 不传递中的字节 name printf 。它传递以下地址 名称 (技术上为其第一个元素的地址) 输出函数 .

    For %d , 输出函数 期待A int 。当您传递地址时,行为不是由C标准定义的。通常(但并非总是),结果是将地址或其部分打印为十进制数字。这个数字与存储在内存中地址处的任何值无关。

    当您将字符串传递给 输出函数 用一个 %s 规范中,地址也会被传递,但是 输出函数 知道,对于字符串,它必须从内存中获取字节。因此,它获取传递的地址,并使用它从内存中加载字节并打印出来。这种情况不会发生在 d ;价值 d 必须直接传递,而不是通过地址传递。

    如果你想重新解释中的字节 名称 作为一个数字,您可以这样做,如下所示:

    #include <stdio.h>
    #include <string.h> // Declares memcpy.
    
    int main(void)
    {
       char name[4] = {'x', '%', 'Q', 0};
       int t;                       // Define a temporary variable.
       memcpy(&t, name, sizeof t);  // Copy bytes into t.
       printf("%d\n", t);           // Show value of t.
    }
    

    请注意,不同的C实现可能代表 int 以不同的方式,特别是通过对其中的字节使用不同的顺序,因此不同的C实现可能会打印不同的值。不同的C实现也可能使用更少或更多的四个字节 int 字符x、%和Q的值也可能不同。

        2
  •  1
  •   trincot Jakube    7 月前

    正如评论中提到的 name 是一个地址,它是一个4个字符的内存指针,打印的是该地址。

    如果你想打印这四个字节表示的整数,那么:

    • 将指针解释为指向的指针 int
    • 取消引用该指针

    所以:

        printf("%d\n", *((int*) name));
    

    必须说,你永远不应该这样做。字节顺序,甚至大小 int 取决于实施。您不应该依赖于一个特定的实现,而应该保持类型一致性。

        3
  •  1
  •   Ray    7 月前

    这里有几件事在起作用。首先,正如Eric指出的那样,您传递的是姓名地址,而不是内容(尽管您会使用%p而不是%d来正确打印)。但是,即使您转换并取消引用参数,以便将该数组的内容解释为整型,它也不一定会打印出与其他类型相同的结果:

    某些CPU会乱序存储多字节对象的字节。这被称为 endianness 的代表。英特尔芯片是小端字节序,其中最低有效字节存储在前面(就像大多数其他现代系统一样。我认为苹果过去使用大端字节序芯片,但我不确定)。因此(对于32位无符号整数),0x00112233实际上会存储为{0x33,0x22,0x11,0x00}。

    将char数组解释为int时的最后一个问题是 对齐 int不一定允许从内存中的任何点开始。细节因系统而异,但通常T类型的对象需要从地址开始 美国。 0 == a % sizeof(T) (或者,如果T较大,则至少为sizeof(int))。因此,如果char数组从一个奇数地址开始,如果你试图将其视为int,你可能会遇到总线错误。

    p.s.在网络数据包中,你确实可以看到大端序表示:由于我们可能想使用与我们不同的端序将int传输到系统,我们首先将其转换为网络端序(大端序)顺序,然后接收系统将其转换成其本机端序。在实践中,这意味着每个系统都会从小字节序转换为大端序,这样接收系统就可以再次将其转换回来,因为没有人再使用大端序表示了。但在这一点上改变标准会带来更多的麻烦。