首页 > 解决方案 > 使用指针算法查找数组大小时动态创建的数组 (int *arr) 和静态创建的数组 (int arr[]) 之间的区别

问题描述

#include <stdio.h>

int main(void)
{
    int arr[] = { 1, 2, 3, 4, 5 };

    //printf("The size of the array is %d", n); //assuming int 4 bytes
    printf("%p - %p: %d\n",(&arr)[1], arr, (&arr)[1] - arr);

    return 0;
}

我知道这(&arr)[1]将给出下一个内存块的地址(数组最后一个元素之后的内存地址)arr并将保存数组中第一个元素的地址。因此,差异将给出它们之间的字节数。上面的代码给出了它应该做的数组的大小。

但我不知道为什么会出现差异/4。我认为这是因为我们正在使用%d. 我尝试了不同的格式说明符,但似乎没有任何效果。回答这个是次要的。

现在,我使用动态内存分配尝试了同样的事情,如下所示:

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

int main(){
    int *arr, i;
    arr = (int*)malloc(10*sizeof(int));
    for (i = 0; i < 10; i++){
        // scanf("%d", &arr[i]);
        arr[i] = i;
    }

    printf("%p - %p: %d\n",(&arr)[1], arr, (&arr)[1] - arr);
    return 0;
}

但结果是不同的。它给出了一个随机值。

然后我尝试了:

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

int main(){
    int *arr, *ptr, i;
    arr = (int*)malloc(10*sizeof(int));
    ptr = arr;
    for (i = 0; i < 10; i++){
        // scanf("%d", &ptr[i]);
        ptr[i]=i;
    }

    printf("%p - %p: %d\n",(&ptr)[1], ptr, (&ptr)[1] - ptr);
    return 0;
}

结果是0。当然,我尝试了不同的可能组合以在语句 and中使用ptrand 。一切都给了。显而易见的原因是,两者的地址相同。arrprintfor loop0

我对这种行为的假设是由于动态创建的数组和静态创建的数组之间的差异。

有人可以解释一下是什么原因吗?

提前致谢。如果我问错了什么,请帮助我纠正自己。

编辑:为了使问题更清楚,我根据社区的建议更改%d为需要的地方。%p干杯!

标签: carrayspointers

解决方案


数组

您正在混合arrayspointers,这是两个不同的对象。让我们做一个心理实验:

想象一下,C 中的每个对象在内存中都有一个地址。所以像这样的声明int a;将为 variable 保留一个内存地址a。您可以使用 来查看该内存地址&a,如下所示:

int a;
printf("&a: %p\n", &a);

数组是保存相同类型数据序列的对象。因此,int数组int在内存中有一个 s 序列。数组的地址是第一个元素的地址(因此您可以访问数组并取消引用它)。

array[0]这也是您使用而不是访问数组的第一个元素的原因array[1]。索引是基地址的位移,由 name 给出array,它指向内存中的第一个元素。

 arr[0]
 |       arr[2]
 |       |
 v       v
 +---+---+---+---+---+
 | 0 | 1 | 2 | 3 | 4 |
 +---+---+---+---+---+

回到你的问题,在编译时编译器知道你的数组的大小,这是静态的。在您的代码中,它有 5 个元素长。int在您的情况下,元素具有类型。因此,您的数组的大小是5 elements * 4 bytes of unit size = 20 bytes(考虑到您的 C int 实现是 4 字节长)。

所以你可以用它做各种技巧,因为编译器知道数组的大小。因此,您可以使宏喜欢SZ(n) (sizeof(n)/sizeof(n[0]))获取元素中数组的大小。测试代码:

/* so1.c
 */

#include <stdio.h>

#define SZ(n)       (sizeof(n)/sizeof(n[0]))

int main(void)
{
    int arr[] = {1, 2, 3, 4, 5};

    printf("Array size (byges)   : %d\n", sizeof(arr));
    printf("Array size (elements): %d\n", SZ(arr));

    printf("Address of arr      : %p\n", arr);
    printf("Address of &arr     : %p\n", &arr);
    printf("Address of arr[0]   : %p\n", &arr[0]);
    printf("Address of &arr[4]  : %p\n", &arr[4]);
    printf("Address of (&arr)[1]: %p\n", (&arr)[1]);

    printf("arr[4] - arr[0]     : %d\n", arr[4] - arr[0]);

    return 0;
}

arr[4]请注意,和之间的地址偏移差异arr[0]16 bytes或 4 个int元素,每个元素大小为 4 个字节。您可能想知道为什么,因为数组是 20 字节,但考虑到最后一个元素数组开头的 16 字节偏移量开始,一直到数组结尾(最后 4 字节将由最后一个元素)。

指向数组的指针和地址运算符

还有一些非常重要的事情需要考虑。鉴于&运算符的属性,当您将其转换为 type 数组时T [n],它会变成一个指向 type 的指针T(*)[n],其计算方式不同,您应该阅读此答案以了解差异。

(T [n]) + 1   => address of T + 1 byte
(T(*)[n]) + 1 => address of T + (n * 1) bytes

这就是为什么(&arr[1])-1在大多数实现中,诸如此类的技巧会指向数组中的最后一个元素,但这既不便携也不符合标准-因此不推荐这样做。


指针

现在考虑以下程序,它使用动态内存:

/* so2.c
 */

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

int main(void)
{
    int *p;
    int i;

    printf("p: %p\n", p);
    printf("&p: %p\n", &p);

    if(!(p = malloc(5 * sizeof *p))) {
        perror("malloc");
        exit(-1);
    }

    printf("AFTER malloc()\n");
    printf("p : %p\n", p);
    printf("&p: %p\n", &p);

    for(i = 0; i < 5; i++) {
        p[i] = i + 1;
    }

    printf("&p[0]: %p\n", &p[0]);
    printf("&p[4]: %p\n", &p[4]);

    free(p);
    return 0;
}

之前malloc,可以看到is的。这意味着它不指向任何地方。但是,exists ( ) 的地址,因为我们必须将它存储在某个地方。调用后,您会看到现在有一​​个值。该值是系统给我们的一块内存的地址。pointer *pNULLpointer&pmallocp

编译器无法在编译时知道那块内存的大小(它怎么会知道操作系统刚刚给我们的东西的地址?!)。事实上,尺寸可能与您要求的尺寸有很大不同。malloc(n)它的兄弟姐妹只保证返回的内存块足以容纳至少n bytes一些东西,但不保证块的大小(或内存可用!)。来自malloc(3) 手册

默认情况下,Linux 遵循乐观的内存分配策略。这意味着当 malloc() 返回非 NULL 时,不能保证内存确实可用。如果发现系统内存不足,OOM 杀手将杀死一个或多个进程。更多信息请参见 proc(5) 中 /proc/sys/vm/overcommit_memory 和 /proc/sys/vm/oom_adj 的描述,以及 Linux 内核源文件 Documentation/vm/overcommit-accounting.rst。

因此,无法知道它的大小。

大小(指针)

当您调用sizeof指针时,它仅返回给定架构中指针的大小,在 32 位机器上为 4 个字节,在 64 位机器上为 8 个字节。当你将地址&操作符强制转换为指针时,它会产生一个指针指向指针 ( T (*)(*)),它仍然是一个指针,并且具有与常规指针相同的大小T(*)- 因为理论上两者都将保存内存地址。

警告我知道谈论内存地址过于简单化,因为 C 语言的标准没有谈论实现的细节。但是,首先考虑这个低级别,然后逐步了解标准的琐碎和特质,这确实很有帮助。K&R C的指针章节是你能得到的最好的参考——阅读并做练习。


推荐阅读