将一个数组分配给具有复杂情况的指针

3
如果我们将一个指针赋值给另一个指针,这被称为“交换指针”。例如,
float *temp, *ptr1, *ptr2;
temp = ptr1;
ptr1 = ptr2;
ptr2 = temp;

然而,如果我们将数组分配给指针,这是不合法的,因为数组实际上不是指针类型。但看看下面的3个例子:

float arr[]={1.2, 1.9, 3.1};
float *ptr;
int i;
ptr = (float *)calloc(3,sizeof(float));
ptr = arr;
for (i=0;i<3;i++){
    printf("ptr[%d]=%f\n",i,ptr[i]);
}

这段代码片段经过编译并正确运行(非法代码也能得到正确的答案?):

ptr[0]=1.200000
ptr[1]=1.900000
ptr[2]=3.100000

如果我在最后一行之后添加free(ptr),即:
float arr[]={1.2, 1.9, 3.1};
float *ptr;
int i;
ptr = (float *)calloc(3,sizeof(float));
ptr = arr;
for (i=0;i<3;i++){
    printf("ptr[%d]=%f\n",i,ptr[i]);
}
free(ptr);  /*add free here*/

这次出现了一个警告:

warning: attempt to free a non-heap object ‘arr’ [-Wfree-nonheap-object]

int i;
int flag=0;
float arr1[3]={1.07,3.01,5.02};
float arr2[3]={2.07,6.01,9.02};
float arr3[3]={3.07,8.01,0.02};
float *ptr;
ptr = (float *)calloc(3,sizeof(float));
if(flag==0){
    ptr = arr1;
}
else if(flag==1){ 
    ptr = arr2;
}
else{ 
    ptr = arr3;
}
for (i=0;i<3;i++){
    printf("ptr[%d]=%f\n",i,ptr[i]);
}

使用不同的标记可以正确运行,但如果在最后一行添加free(ptr),它仍然会像之前一样出现警告。

有人能帮忙分析原因吗?


1
在一行上,您分配足够的字节来容纳三个浮点数,并设置 ptr 的值以保存该内存位置。在下一行中,将 ptr 的值更改为保存 arr 数组的地址,使之前分配的内存变成了孤立状态。由于 ptr 指向的内存实际上是一个包含三个浮点数的文字数组,因此您无法释放它。您应该释放的内存已经泄漏了。 - Tibrogargan
1
在调用任何堆分配函数时:malloc calloc realloc,请注意以下两点:1)返回类型是void*,可以分配给任何指针。强制转换只会使代码变得混乱,难以理解、调试等。2)始终检查(!= NULL)返回值以确保操作成功。 - user3629249
2
关于 ptr = arr1; 和类似的语句。这会覆盖通过调用 calloc() 设置在指针中的值,导致内存泄漏。也许你想表达的是:memcpy( ptr, arr1, sizeof( arr1) ); 注意:在 C 中,引用数组名称会降级为数组第一个字节的地址。 - user3629249
@user3629249 - 我知道你的意思,但从技术上讲,数组被“转换为具有指向数组对象初始元素的类型为'指向类型'的表达式”(退化有点奇怪,但你会看到衰减--它本身不在标准中,但是它是常见理解)。请参见:C11标准-6.3.2.1左值、数组和函数设计符号(p3) - David C. Rankin
4个回答

3
ptr = (float *)calloc(3,sizeof(float));

在这里,你将ptr设置为一个足够大的malloc内存块,可以容纳3个浮点数。然后在下一行:
ptr = arr;

您将ptr的值用arr覆盖,导致内存泄漏。这是合法的,因为在这样的赋值中,数组衰减为指向其第一个元素的指针。
然后,当您打印ptr指向的值时,您会得到arr中的值,因为那是ptr的指向位置。
这也是为什么对ptr调用free无效,因为它不再指向已分配的内存。
至于最后一段代码,这是无效的初始化程序:
float arr1[3]={{1.07,3.01,5.02}};

当我运行这段代码时,第二个和第三个值的flag=0 显示为0,而 flag=1flag=2 显示了所有期望的值。
你只需要一个大括号:
float arr1[3]={1.07,3.01,5.02};

1
对于最后一段代码,即使我在最后一行添加 free(ptr);,它仍然会像之前一样出现警告。 - coco
1
@coco 是的,原因相同:您使用本地数组的地址覆盖了malloc分配的内存。 - dbush
1
所以这段代码片段仍然是无用的,因为我们在使用完它们后必须释放内存。这是否意味着我必须找到另一种将数组分配给指针的方法,比如“for”循环? - coco
2
@coco 你必须将本地数组复制到分配的内存(使用memcpy或循环),或者完全消除分配。 - dbush

2

无错误解决方案

#include<stdio.h>  
#include<stdlib.h>

int main()
{
    float arr[]={1.2, 1.9, 3.1};
    float *ptr;
    int i;
    ptr = (float *)calloc(3,sizeof(float*));

    if(ptr == NULL){
     perror("calloc");
     return -ENOMEM;
    }
    //ptr = arr;

    // here you need to do something like this
    ptr[0] = arr[0];
    ptr[1] = arr[1];
    ptr[2] = arr[2];

    for (i=0;i<3;i++){
       printf("ptr[%d]=%f\n",i,ptr[i]);

           }
        free(ptr);
}

2
不是错误,但没有必要对malloc(或callocrealloc)的返回进行强制转换,这是不必要的。请参见:Do I cast the result of malloc? -- 而且你的大括号格式化方式确实很有趣... - David C. Rankin
是的,你说得对,但是为了在其他语言(如C++)之间的可移植性,我喜欢这样做。 - Jeet Parikh
这就是为什么有所谓的C标准和一个单独的C++标准文件。此外,在使用内存之前,应该验证calloc的返回值,例如(if (ptr != NULL))。分配函数在失败时返回NULL(它们确实会失败)。回答问题时,请扮演老师的角色,帮助新学生学习编程。确保他记得你是其中一位好老师 :) - David C. Rankin
完成了!谢谢,下次我会确保一切都清晰明了.. :) - Jeet Parikh

1
当您将指针指向数组时,您的代码中绝对没有任何非法行为。它甚至不会给您警告。
指针是指针,可以指向任何东西。有时您可能需要进行类型转换,但指向相同基本类型的数组不仅是完全有效的,而且非常常见。
但是,您只能在堆上分配的内存上使用free(malloc、calloc等)。数组是在栈上分配的,不需要被释放,并且正如您已经看到的,如果您这样做,将会得到运行时错误。

1
但是对于“交换指针”(就像第一个例子一样),我们可以“释放”指针。这是因为动态分配的内存仍然存在,而数组可以使用常量指针覆盖它,使指针不再是动态分配的。 - coco
@coco 在第一个示例中,您根本没有分配任何内存,因此不能释放它们。 - klutt

1

分配一个数组

C语言中不能直接分配数组。

你需要逐个元素地进行分配。

这里会发生什么?

float arr[]={1.2, 1.9, 3.1};
float *ptr;

ptr = arr;

这意味着您将 ptr 指定为 arr [0] 的地址。这被称为"数组衰减为指针(其第一个元素的地址)"

没有复制任何数组元素!

要证明这一点,只需执行

arr[0] = 42.;

下一步,然后运行打印循环

for (int i = 0; i < 3; ++i) 
{
  printf("ptr[%d] = %f\n", i, ptr[i]);
}

你会得到(类似于):
ptr[0] = 42.000
ptr[1] = 1.9000
...

实际上它可以被复制。请看第二个代码片段,它可以返回正确的值。 - coco
@coco:你有没有阅读并尝试我的建议来证明在你展示的三种情况中都没有“复制”任何元素? - alk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接