当我们将char *转换为int *时,在后台或内存中会发生什么

时间:2019-07-08 10:49:20

标签: c casting

我正在学习指针的类型转换,并随机进入该程序

#include <stdio.h>
main() { 
  char* p="01234567890123456789";
  int *pp = (int *)p;              
  printf("%d",pp[0]);
}

在执行上述程序时,输出为858927408 这些随机数是什么,它们来自何处? 后台或内存中发生了什么?

编辑:如果我写printf("%c",pp[0]);,则输出为0,这是正确的,但是当我将pp[0]更改为pp[1]时,输出为4,但如何?

5 个答案:

答案 0 :(得分:3)

如果用十六进制(%x)表示结果,则可以看到:

858927408 = 0x33323130
  • 0x33'3'的ASCII码
  • 0x32'2'的ASCII码
  • 0x31'1'的ASCII码
  • 0x30'0'的ASCII码

因此,您只显示存储0123456...的内存,但是由于处理器是little endian,因此您会看到代码反转。

在内存中,您拥有(六进制)

30 31 32 33 34 35 36 37 38   # 0 1 2 3 4 5 6 7 8
39 30 31 32 33 34 35 36 37   # 9 0 1 2 3 4 5 6 7
38 39 00                     # 8 9\0  

printf("%d...")中,您将前4个字节读取为小端整数,因此它将显示0x33*0x1000000 + 0x32*0x10000 +0x31*0x100 +0x30的结果


对于%c,情况有所不同:

如果您写printf("%c", pp[0]),则将尝试从0x33323130打印一个字符,因此0x30被保留(在您的情况下,在某些情况下可能是UB,不确定),因此显示“ 0”,ASCII码为0x30

如果您写printf("%c", pp[1]),则将尝试从0x37363534打印一个字符,因此0x34被保留,因此显示“ 4”,ASCII码为0x34 < / p>

答案 1 :(得分:2)

  1. 如果您的C实现使用ASCII,则字符串"01234567890123456789"的前四个字节为48、49、50和51(十六进制0x30、0x31、0x32和0x33),这是用于字符“ 0”,“ 1”,“ 2”和“ 3”。
  2. (int *)ppchar *转换为int *。指针转换未完全由C标准定义。请参阅下面的注释。如果没有对齐问题,则在大多数C实现中,此转换的结果将指向p指向的相同位置。
  3. 已将pp设置为(int *)ppp[0]提取pp处的字节并将其解释为int。在您的实现中,int对象有四个字节,并且字节在最低寻址的内存中以最低有效字节排序。因此,从内存中读取了字节0x30、0x31、0x32和0x33,并将其形成为整数0x33323130(十进制858927408)。

关于指针转换和别名的注意事项

与指针转换有关的三件事与之相关:

  • 如果对齐方式不正确,则C标准未定义指针转换。特别是,在许多C实现中,int对象应对齐四字节,而char对象则可以对齐。如果p中的地址未针对int正确对齐,则表达式(int *)p可能会导致程序崩溃或导致不良结果。
  • 即使对齐正确,C标准也不保证将常规char *转换为int *的结果是什么,除了将结果转换回char *会产生原始指针(或等效指针)。在许多C实现中,此转换将产生一个指向相同地址的指针,只是类型不同。
  • 表达式pp[0]像访问p一样访问int处的字节。这违反了C标准中称为别名规则的规则,该规则说对象只能通过使用正确类型的表达式访问其值。关于正确的类型有一些详细信息,但是int永远不是char(或几个char)的正确类型。违反此规则时,C标准不会定义行为。

最后一点很重要,因为C实现可能支持也可能不支持别名。一些C实现支持别名(这意味着即使C标准没有定义,也可以定义行为),因为它已经被广泛使用,并且他们希望支持使用它的现有代码,或者因为某些类型的软件需要它。一些C实现不支持别名,因为这使它们可以更好地优化程序。 (如果编译器可以假设int *从未指向float,则在通过float指针进行赋值后,它可能能够避免重新加载int数据时,因为这些赋值不能更改float数据。)某些编译器具有开关,因此您可以启用或禁用别名支持。

由于别名会破坏您的程序,因此您应该了解它的规则,在不需要时避免使用它,并知道如何在需要时启用它。在这种情况下,不需要别名来检查将字符串的字节重新解释为int的结果。一种安全的方法是将字节复制到int中,如下所示:

char *p = "01234567890123456789";
int i;
memcpy(&i, p, sizeof i);
printf("%d\n", i);

答案 2 :(得分:1)

这是((51×256+50)×256+49)×256+48的结果,其中51是ASCII代码“ 3”,而50是ASCII代码“ 2”,依此类推。实际上,pp[0]指向4个字节的内存(int为4个字节),而这4个字节为“ 0123”,并且您的计算机上的int为低位字节序,因此为'0'(数字为48) )是LSB,而'3'是MSB。

p[1]p[0]之后的一个字节,因为p是指向字节数组的指针,而pp[1]pp[0]之后的4个字节,因为{{1} }是指向int数组的指针,并且int是4个字节。

答案 3 :(得分:1)

858927408转换为十六进制时为0x33323130

在您的系统上,您显然具有 little-endian 格式。以这种格式,整数的LSB首先存储。

字符串的前4个字节取整数。 "0123"的ascii值分别为0x30, 0x31, 0x32, 0x33。由于这是小端。整数的LSByte为0x30,整数的MSbyte为0x33

这就是您获得0x33323130作为输出的方式。

编辑关于OP中的其他问题

  

如果我写printf(“%c”,pp [0]);然后输出为0,这是正确的,但是当我将pp [0]更改为pp [1]时,输出为4,但是怎么办?

%c中有printf并提供整数参数时,您正在将整数转换为字符,即LS字节取0x30,并打印为ASCII。

对于pp[1],这是数组中的下一个整数,后4个字节。因此,在这种情况下,LS字节将为0x34,并且在转换为ASCII后将打印4

答案 4 :(得分:0)

它只是将int对象的起始地址设置在字符串的开头。 int的实际值将取决于字节序和sizeof(int)。

如果内存中"01234567890123456789"在内存中为{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39 ...},并且字节序较小,并且sizeof(int) == 4的值将为0x0x33323130。如果端点很大,则值将为0x30313233