提问人:peks 提问时间:8/31/2023 最后编辑:Vlad from Moscowpeks 更新时间:8/31/2023 访问量:125
C 语言中指针算术的奇怪行为
strange behaviour with pointer arithmetics in C
问:
请有人能向我解释一下这种奇怪的指针行为吗?我错过了什么吗?!?
开始第一次尝试
int *foo=(int*)malloc(sizeof(int)*4);//allocates memory for 4 integer with size of sizeof(int) foo points to the address of the first element- foo=&foo[0]
for (int i = 0; i < 4; i++)
{
*foo=i+1;
foo++;
printf("foo[%d] :%d \n",i,foo[i]);
}
Otput 是这样的
foo[0] :0
foo[1] :0
foo[2] :0
foo[3] :0
结束第一次尝试
让我们在代码中添加一个小东西:
开始第二次尝试
int *foo=(int*)malloc(sizeof(int)*4);//same as first attempt
int* ptr=foo;//???????????????????? why this is necessary?
printf("Value of foo: %d value of ptr is: %d &foo[0] %d\n",foo,ptr, &foo[0]);
for (int i = 0; i < 4; i++)
{
*ptr=i+1;//assign and update via ptr
ptr++;
printf("foo[%d] :%d \n",i,foo[i]);
}
输出
Value of foo: 1322628816 value of ptr is: 1322628816 &foo[0] 1322628816
foo[0] :1
foo[1] :2
foo[2] :3
foo[3] :4
结束第二次尝试
注意:我使用 Ubuntu 18.04、gcc (Ubuntu 11.4.0-2ubuntu1\~18.04) 11.4.0 和 GNU C 库 (Ubuntu GLIBC 2.27-3ubuntu1.6) 稳定发布版本 2.27
我完全尝试了我上面写的
答:
执行此操作后,不再指向数组的开头,而是指向数组的第四个元素。当您尝试访问 时,您正在访问该元素后面的元素位置,即 .foo++
foo
i+1
foo[i]
i
original_foo[i+1+i]
它在第二个版本中有效,因为要索引的指针不是要递增的指针。所以继续指向数组的开头,并且是您刚刚分配的元素。foo
foo[i]
*foo=i+1;
这将初始化您编辑的四个区域中的第一个。int
int
malloc()
foo++;
这就引出了第二个。foo
int
printf("foo[%d] :%d \n",i,foo[i]);
您正在打印 ,即第二个(未初始化)调用 UB。foo + i
int
从那里开始,事情只会变得更糟。循环的第二次迭代(使用 )...i == 1
*foo=i+1;
foo
仍然指向第二个,所以你写在那里......int
1
foo++;
foo
现在指向第三个......int
printf("foo[%d] :%d \n",i,foo[i]);
...然后你打印 ,这是第三个加号——第四个加号,它又是未初始化的(同样是未定义的行为)。foo + i
int
1
int
在循环的第三次和第四次迭代中,你最终会从你甚至没有编辑过的记忆中读取。malloc()
你的第二个例子是有效的,因为你增加了写作,(作为索引)增加了阅读。由于您不再进行双倍递增,因此第二个示例有效。ptr
i
foo
当然,对于第一个示例,正确的解决方案是一致地使用而不是递增 foo
。foo[i]
for (int i = 0; i < 4; i++)
{
*foo=i+1; // 1
foo++; // 2
printf("foo[%d] :%d \n",i,foo[i]); // 3
}
- 在这里,您可以分配给点。这本身并没有错。
i+1
int
foo
- 在这里,您可以指出分配的内存中的下一个元素。您现在已经丢失了基本指针 - 除非您稍后通过向后计数或在循环开始之前保存指针来恢复它。
foo
- 在您添加到基指针指向的位置。你现在已经有效地做到了,它很快就会出界。
foo[i]
i
*(original_foo + i + i)
一个惯用的解决方案是完全不改变:foo
for (int i = 0; i < 4; i++) {
foo[i] = i + 1;
printf("foo[%d] :%d \n", i, foo[i]);
}
关于:
int* ptr=foo;//???????????????????? why this is necessary?
这是我在上面第 2 点中提到的基指针的保存,这是一种可能的解决方法。在下面的循环中,您只更改并让基指针保持不变。取消引用(与 相同)会执行正确的操作。ptr
foo
foo[i]
*(foo + i)
for (int i = 0; i < 4; i++)
{
*ptr=i+1; // 1
ptr++; // 2
printf("foo[%d] :%d \n",i,foo[i]); // 3
}
- 同样,分配给 where 点。
i+1
ptr
- 暂时领先一步。
int*
- 取消引用 (),取消引用之前指向的相同地址。
foo[i]
*(foo + i)
ptr
ptr++
首先,我们来看第一个代码片段
int *foo=(int*)malloc(sizeof(int)*4);//allocates memory for 4 integer with size of sizeof(int) foo points to the address of the first element- foo=&foo[0]
for (int i = 0; i < 4; i++)
{
*foo=i+1;
foo++;
printf("foo[%d] :%d \n",i,foo[i]);
}
首先,您使用函数为 4 个类型的对象分配了一个数组int
malloc
int *foo=(int*)malloc(sizeof(int)*4);//allocates memory for 4 integer with size of sizeof(int) foo points to the address of the first element- foo=&foo[0]
分配的内存未初始化。它可以包含所有零或一些随机值。也就是说,内存包含不确定的值。
在 for 循环中
for (int i = 0; i < 4; i++)
{
*foo=i+1;
foo++;
printf("foo[%d] :%d \n",i,foo[i]);
}
您正在为每个对象赋值i + 1
*foo=i+1;
但随后你正在增加指针
foo++;
此外,使用指针算术试图输出甚至不存储在分配的内存中的值。foo[i]
因此,可以访问未初始化的内存,甚至可以访问已分配的内存之外的内存。所以这个电话printf
printf("foo[%d] :%d \n",i,foo[i]);
调用未定义的行为。
如何更改for循环以输出有效值?
只是不要增加指针。那就是写
for (int i = 0; i < 4; i++)
{
foo[i] = i+1;
printf("foo[%d] :%d \n",i,foo[i]);
}
在这种情况下,指针仍将具有已分配内存范围的地址,您可以在循环后释放分配的内存,例如foo
free( foo );
它只是以这样一种方式发生,即您正在访问的内存包含零。但是,通常,输出可能与此不同
foo[0] :0
foo[1] :0
foo[2] :0
foo[3] :0
并且可以显示任何随机值。
在第二个代码片段的 for 循环中
for (int i = 0; i < 4; i++)
{
*ptr=i+1;//assign and update via ptr
ptr++;
printf("foo[%d] :%d \n",i,foo[i]);
}
表达式 *ptr 和 是等效的。foo[i]
i
表示数组的元素。例如,如果等于 then 并 yeilds 相同的元素。当增加时,表达式的计算方式为 。但它与 和 then 使用相同,因为 after 等于 。换句话说,相同的表达式可以像使用逗号运算符一样表示或类似于表达式。i-th
i
0
*ptr
foo[0]
i
foo[1]
*( foo + 1 )
ptr++
*ptr
ptr++
ptr
ptr + 1
foo[1]
*( foo + 1 )
*( ( ptr = ptr + 1 ) )
*( ptr++, ptr )
从 C 标准(6.5.2.1 数组下标)
2 后缀表达式后跟方括号中的表达式 [] 是数组对象元素的下标名称。这 下标运算符 [] 的定义是 E1[E2] 等同于 (*((E1)+(E2)))。由于转换规则适用于 二进制 + 运算符,如果 E1 是数组对象(等效地,指针 到数组对象的初始元素),并且 E2 是一个整数, E1[E2] 表示 E2 的第 1 个元素(从零开始计数)。
这并不奇怪。
第一次尝试方法是错误的。正确的方法是:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
//allocates memory for 4 integer with size of sizeof(int) foo points
//to the address of the first element- foo=&foo[0]
int *foo=(int*)malloc(sizeof(int)*4);
if(foo == NULL){
printf("malloc memory failed\n");
return -1;
}
memset(foo, 0, sizeof(int)*4);
for (int j = 0; j < 4; j++){
printf("foo[%d] address is %p\n", j, &foo[j]);
}
printf("\n");
for (int i = 0; i < 4; i++)
{
foo[i]=i+1;
printf("foo[%d] :%d \n",i,foo[i]);
}
free(foo);
return 0;
}
运行它将输出:
foo[0] address is 0x5635caaac2a0
foo[1] address is 0x5635caaac2a4
foo[2] address is 0x5635caaac2a8
foo[3] address is 0x5635caaac2ac
foo[0] :1
foo[1] :2
foo[2] :3
foo[3] :4
第二次尝试是正确的:
int* ptr=foo;//???????????????????? why this is necessary?
ptr 的值类型为 ,ptr 的地址值为 ,所以 when 等于 。int*
&foo[0]
ptr + 1
value of address of ptr + sizeof(int) = &foo[0] + 4
当你 时,你可以看到 ,关于数组 在数组中查看更多详细信息 为什么减去数组中连续值的地址会得到不同的值,当存储和不存储在变量中时?int *foo=(int*)malloc(sizeof(int)*4);
foo
int foo[4]
评论
foo++;
- 在 1 中,您更改了 foo,使 foo[0] 不再指向缓冲区中的第一个 int。它指向缓冲区[1]。缓冲区[2]。缓冲区[3]。缓冲区[ OVERRUN ]%p
void *
printf
printf("Value of foo: %p value of ptr is: %p &foo[0] %p\n",(void*)foo,(void*)ptr, (void*)&foo[0]);