我正在学习指针的类型转换,并随机进入该程序
#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
,但如何?
答案 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)
"01234567890123456789"
的前四个字节为48、49、50和51(十六进制0x30、0x31、0x32和0x33),这是用于字符“ 0”,“ 1”,“ 2”和“ 3”。(int *)p
将p
从char *
转换为int *
。指针转换未完全由C标准定义。请参阅下面的注释。如果没有对齐问题,则在大多数C实现中,此转换的结果将指向p
指向的相同位置。pp
设置为(int *)p
,pp[0]
提取pp
处的字节并将其解释为int
。在您的实现中,int
对象有四个字节,并且字节在最低寻址的内存中以最低有效字节排序。因此,从内存中读取了字节0x30、0x31、0x32和0x33,并将其形成为整数0x33323130(十进制858927408)。与指针转换有关的三件事与之相关:
int
对象应对齐四字节,而char
对象则可以对齐。如果p
中的地址未针对int
正确对齐,则表达式(int *)p
可能会导致程序崩溃或导致不良结果。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