以下是一个简单的代码段:
int main()
{
int *p;
p=(int*)malloc(sizeof(int));//allocate m/y 4 1 int
printf("P=%p\tQ=%p",p,p+2);
}
在一次示例运行中,它给出了如下输出:
P=0x8210008 Q=0x8210010
P的起始地址是-P = 0x8210008,下一个字节是0x8210009,下一个字节是0x821000A,下一个字节是0x821000B。所以int的4个字节在那里结束。 我们没有使用malloc分配更多内存。 那么p + 2如何将我们引向0x8210010,这是P(0x8210008)之后的8个字节。
答案 0 :(得分:5)
因为它将它视为指针的整数元素偏移量。您已为单个整数分配了一个数组。当您要求p+2
时,它与&p[2]
相同。如果您想从头开始使用两个字节,则需要先将其转换为char*
:
char *highWordAddr = (char*)p + 2;
答案 1 :(得分:4)
C很高兴让你做任何你喜欢的指针运算。仅仅因为p+2
看起来像任何其他地址并不意味着它是有效的。事实上,在这种情况下,它不是。
每当你看到指针算术时你都不要超出你的分配范围。
答案 2 :(得分:4)
首先,您打印了一个地址的事实并不意味着在该地址分配了内存。您只需添加数字并生成其他数字。
其次,你加上两个你得到的数字的原因是比基地址大八倍而不是两个大于基地址的原因是因为,当你在C中的指针中添加整数时,算术是用指向的-to元素,而不是内存中的字节数(除非指向的元素是字节)。假设你有一个int数组,比如int x[8]
,你有一个指向x[3]
的指针。向该指针添加两个指针会产生指向x[5]
的指针,而不是指向x[3]
开头之外的两个字节的指针。重要的是要记住C是一个抽象,C标准指定了抽象内部发生的事情。在C抽象中,指针算法对元素的数量起作用,而不是在原始内存地址上。 C实现(编译器和将C代码转换为程序执行的工具)需要执行对原始内存地址执行的任何操作,以实现C标准指定的抽象。通常,这意味着编译器在将整数添加到指针时将整数乘以元素的大小。因此,两个乘以4(在int
为四个字节的机器上),并将结果中的八个添加到基址。
第三,你不能依赖这种行为。 C标准仅为指向数组内对象的指针定义指针算法,包括数组末尾的一个虚构对象。另外,指向单个对象的指针就像一个元素的数组。因此,如果指针指向p
,则可以计算p+0
或p+1
,因为它们指向数组中唯一的对象({{1} })和虚构对象超出数组中最后一个元素(p+0
)。您不允许计算p+1
或p-1
,因为它们位于数组之外。请注意,这不是取消引用指针(尝试在计算地址读取或写入内存)的问题:即使仅计算地址也会导致C标准未定义的行为:您的程序可能会崩溃,它可能会给你“正确”的结果,或者它可能会删除你帐户中的所有文件,所有这些行为都符合C标准。
仅仅计算越界地址不太可能产生这种奇怪的行为。但是,该标准允许它,因为一些计算机处理器具有不寻常的地址方案,需要比简单算术更多的工作。也许在平坦地址空间之后的第二常见地址方案是基地址和偏移方案。在这种方案中,四字节指针的高16位可能包含基址,低16位可能包含偏移量。对于给定的基址b和偏移量o,相应的虚拟地址可能是4096 * b + o。 (这种方案只能处理2个 20 字节,并且许多不同的base和offset值可以指相同的地址。例如,base 0和offset 4096指的是与base相同的地址1和偏移量0.)使用基本和偏移方案,编译器可以通过仅添加偏移量并忽略基数来实现指针算法。 (这样的C实现可以支持最多65536个字节的数组,这个范围只能通过偏移量来解决。)在这样的实现中,如果你有指向int p+2
的指针,其编码为0x0000fffc(基数为0,偏移65532),p
是四个字节,然后int
将具有值0x00000004,而不是八个更大的值(0x00010004)。
这是一个示例,其中指针算术产生您不希望从平面地址机器中获得的值。很难想象一个实现,其中根据C标准无效的指针算法会产生崩溃。但是,请考虑一种实现,其中内存必须由进程手动交换,因为处理器没有硬件来支持虚拟内存。在这样的实现中,指针可能包含内存中描述磁盘位置的结构的地址以及用于管理内存交换的其他信息。在这样的实现中,执行指针运算可能需要读取内存中的结构,因此无效指针算法可能会读取无效地址。
答案 3 :(得分:1)