C 语言中指针算术的奇怪行为

strange behaviour with pointer arithmetics in C

提问人:peks 提问时间:8/31/2023 最后编辑:Vlad from Moscowpeks 更新时间:8/31/2023 访问量:125

问:

请有人能向我解释一下这种奇怪的指针行为吗?我错过了什么吗?!?

开始第一次尝试

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

我完全尝试了我上面写的

c malloc 指针算术

评论

0赞 Barmar 8/31/2023
请重新发布您的代码。查看 stackoverflow.com/help/formatting
7赞 Dave S 8/31/2023
foo++;- 在 1 中,您更改了 foo,使 foo[0] 不再指向缓冲区中的第一个 int。它指向缓冲区[1]。缓冲区[2]。缓冲区[3]。缓冲区[ OVERRUN ]
0赞 Weather Vane 8/31/2023
请发布完整的代码,一个最小可重现的例子,用户可以完全按原样复制/粘贴和编译的最短代码。
0赞 Ian Abbott 8/31/2023
注意:用于打印指针值。严格来说,要打印的指针值应该是类型,因此您需要对调用中打印的指针参数进行类型转换,如下所示: .%pvoid *printfprintf("Value of foo: %p value of ptr is: %p &foo[0] %p\n",(void*)foo,(void*)ptr, (void*)&foo[0]);

答:

4赞 Barmar 8/31/2023 #1

执行此操作后,不再指向数组的开头,而是指向数组的第四个元素。当您尝试访问 时,您正在访问该元素后面的元素位置,即 .foo++fooi+1foo[i]ioriginal_foo[i+1+i]

它在第二个版本中有效,因为要索引的指针不是要递增的指针。所以继续指向数组的开头,并且是您刚刚分配的元素。foofoo[i]

4赞 DevSolar 8/31/2023 #2
*foo=i+1;

这将初始化您编辑的四个区域中的第一个。intintmalloc()

foo++;

这就引出了第二个fooint

printf("foo[%d] :%d \n",i,foo[i]);

您正在打印 ,即第二个(未初始化)调用 UB。foo + iint

从那里开始,事情只会变得更糟。循环的第二次迭代(使用 )...i == 1

*foo=i+1;

foo仍然指向第二个,所以你写在那里......int1

foo++;

foo现在指向第三个......int

printf("foo[%d] :%d \n",i,foo[i]);

...然后你打印 ,这是第三个加号——第四个加号,它又是未初始化的(同样是未定义的行为)。foo + iint1int

在循环的第三次和第四次迭代中,你最终会从你甚至没有编辑过的记忆中读取。malloc()


你的第二个例子是有效的,因为你增加了写作,(作为索引)增加了阅读。由于您不再进行双倍递增,因此第二个示例有效。ptrifoo

当然,对于第一个示例,正确的解决方案是一致地使用而不是递增 foofoo[i]

3赞 Ted Lyngmo 8/31/2023 #3
for (int i = 0; i < 4; i++)
{
    *foo=i+1;                           // 1
    foo++;                              // 2
    printf("foo[%d] :%d \n",i,foo[i]);  // 3
}
  1. 在这里,您可以分配给点。这本身并没有错。i+1intfoo
  2. 在这里,您可以指出分配的内存中的下一个元素。您现在已经丢失了基本指针 - 除非您稍后通过向后计数或在循环开始之前保存指针来恢复它。foo
  3. 在您添加到基指针指向的位置。你现在已经有效地做到了,它很快就会出界。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 点中提到的基指针的保存,这是一种可能的解决方法。在下面的循环中,您只更改并让基指针保持不变。取消引用(与 相同)会执行正确的操作。ptrfoofoo[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
}
  1. 同样,分配给 where 点。i+1ptr
  2. 暂时领先一步。int*
  3. 取消引用 (),取消引用之前指向的相同地址。foo[i]*(foo + i)ptrptr++
0赞 Vlad from Moscow 8/31/2023 #4

首先,我们来看第一个代码片段

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 个类型的对象分配了一个数组intmalloc

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-thi0*ptrfoo[0]ifoo[1]*( foo + 1 )ptr++*ptrptr++ptrptr + 1foo[1]*( foo + 1 )*( ( ptr = ptr + 1 ) )*( ptr++, ptr )

从 C 标准(6.5.2.1 数组下标)

2 后缀表达式后跟方括号中的表达式 [] 是数组对象元素的下标名称。这 下标运算符 [] 的定义是 E1[E2] 等同于 (*((E1)+(E2)))。由于转换规则适用于 二进制 + 运算符,如果 E1 是数组对象(等效地,指针 到数组对象的初始元素),并且 E2 是一个整数, E1[E2] 表示 E2 的第 1 个元素(从零开始计数)。

0赞 Tom 8/31/2023 #5

这并不奇怪。

第一次尝试方法是错误的。正确的方法是:

#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 + 1value of address of ptr + sizeof(int) = &foo[0] + 4

当你 时,你可以看到 ,关于数组 在数组中查看更多详细信息 为什么减去数组中连续值的地址会得到不同的值,当存储和不存储在变量中时?int *foo=(int*)malloc(sizeof(int)*4);fooint foo[4]