【问题标题】:What's the difference between int array[] and int* array and where is address of array stored (C)?int array[] 和 int* array 之间有什么区别以及存储的数组地址(C)在哪里?
【发布时间】:2022-10-04 22:14:05
【问题描述】:

假设我们有一个这样的程序

int main() {
    int array[3] = { 1, 2, 3 };
    int* ptr = array; // <--- Didn't have to use a "&"
    printf("%d\n", *array);
    printf("%d\n", *ptr);
    
    return 0;
}

我们期望得到:

1
1

我的问题是

  1. 我读到here 说“数组”不是“左值”。这是什么意思?
  2. “数组”只是内存块的名称吗?如果是,该块的地址存储在哪里? int* ptr = array 意味着“数组”块的地址必须存储在“数组”中,对吧?
  3. 它和这样的东西有什么不同? “点”不也是一块内存的名字吗?
    struct Point { int x; int y; };
    int main() {
        struct Point point = { 1, 2 };
        struct Point* ptr = &point; // <--- Have to use a "&"
        printf("%d\n", point.x);
        printf("%d\n", ptr->x);
    
        return 0;
    }
    

【问题讨论】:

    标签: arrays c pointers memory


    【解决方案1】:
    1. 虽然“左值”的整个概念很复杂,但在这种情况下,它主要意味着您不能分配给它。你不能做array = something;。但是你可以做ptr = something;,因为ptr 是一个左值。
    2. 数据存储的细节取决于实现,但通常一个自动数组将存储在堆栈帧中,就像任何其他自动变量一样。
    3. 不同之处在于,在许多情况下,数组“衰减”为指向其第一个元素的指针。所以当你写
      int *ptr = array;
      

      相当于

      int *ptr = &array[0];
      

    【讨论】:

    • “衰减”过程是编译器所做的,还是运行时“功能”?我认为我应该学习汇编才能完全理解这一点?
    • 这是编译器所做的事情。它基本上只是将array 视为您编写了&amp;array[0]
    • 这应该在任何 C 教科书或教程中关于数组的章节中进行解释。
    • 谢谢,我会看看那个。
    【解决方案2】:

    左值是 void 以外的对象类型的表达式,它可能指定一个对象(可能存储值的一块内存),以便可以读取或修改该对象。左值可能包括变量名,如x,数组下标表达式,如a[i],成员选择表达式,如foo.bar,指针取消引用,如*p,等等。一个好的经验法则是,如果它可以成为= 运算符,那么它是一个左值。

    数组很奇怪。数组表达式是一个左值,但它是一个不可修改左值;它指定一个对象,但它不能成为分配的目标。当你在 C 中声明一个数组时

    int a[N];
    

    你在记忆中得到的东西看起来像这样:

       +---+
    a: |   | a[0]
       +---+
       |   | a[1]
       +---+
       |   | a[2]
       +---+
        ...
    

    没有目的a 与单个数组元素分开;没有什么可分配的名为aa 代表整个数组,但 C 没有定义 = 运算符来处理整个数组。

    简短的历史课 - C 源自一种名为 B 的早期语言,当您在 B 中声明一个数组时:

    auto a[N];
    

    你得到了这样的东西:

       +---+
    a: |   | -------------+
       +---+              |
        ...               |
       +---+              |
       |   | a[0] <-------+
       +---+
       |   | a[1]
       +---+
       |   | a[2]
       +---+
        ...
    

    在 B 中,a曾是一个单独的对象,它存储了数组第一个元素的偏移量。数组下标操作a[i]定义*(a + i) - 给定存储在a 中的起始地址,偏移i1从该地址并取消引用结果。

    在设计 C 时,Ritchie 想要保留 B 的数组行为 (a[i] == *(a + i)),但他不想保留该行为所需的显式指针。相反,他创建了一个规则,只要数组表达式不是sizeof_Alignof 或一元&amp; 运算符的操作数,它就会从“N 元素数组”类型转换或“衰减”的T”到“指向T”的指针,表达式的值是第一个元素的地址。

    表达式 a[i] = *(a + i) 的工作方式与 B 中的相同,但不是存储a中第一个元素的地址,我们计算我们需要的那个地址(这是在翻译过程中完成的,而不是运行时)。但这意味着您也可以将[] 下标运算符与指针一起使用,所以ptr[i] 做同样的事情:

       +---+                            +---+
    a: |   | a[0] (ptr[0]) <------ ptr: |   |
       +---+                            +---+
       |   | a[1] (ptr[1])
       +---+
       |   | a[2] (ptr[2])
       +---+
        ...
    

    这就是为什么a 不能成为赋值目标的原因——在大多数情况下,它“衰减”为等于&amp;a[0] 的指针值,并且价值观不能作为任务的目标。

    您无法更改某物的地址 - 您只能更改存储在给定地址的值。


    1. B 是一种无类型语言 - 一切都存储为一个单词。

    【讨论】:

    • 这正是让我感到困惑的地方,我想象它可以像在 B 中那样工作。非常感谢。
    【解决方案3】:
    • 我在这里读到“数组”不是“左值”。这是什么意思?

    据推测,作者的意思是 C 没有定义整个数组赋值的行为。也就是说,这不符合语言规范:

        int array1[3] = { 1, 2, 3 };
        int array2[3] = array1;      // NOT ALLOWED
    
        array2 = array1;             // NOT ALLOWED
    

    但是,这与语言规范使用的术语“左值”的定义不一致:

    一个左值是一个表达式(对象类型不是 void),它可能指定一个 目的 [...]

    “左值”这个名称最初来自赋值表达式 E1 = E2,其中需要左操作数 E1 是一个(可修改的)左值。将其视为表示对象“定位器值”可能更好。

    (C17,第 6.3.2.1/1 段和脚注 65)

    根据该定义,array一个左值。但它不是一个可修改左值。

    • “数组”只是内存块的名称吗?

    是的,这是一种合理的看待方式。并且非常符合上述“左值”的定义。

    如果是,该块的地址存储在哪里?

    为什么地址需要存储在任何地方?

    int* ptr = array 意味着 “数组”块的地址必须存储在“数组”中,对吗?

    不,这意味着编译器必须有一种方法将名称 array 与其所代表的存储相关联,以便编译的程序在运行时正确运行。

    在实践中,是的,在编译的程序中需要对数组的位置进行某种表示,但这种表示不是程序的 C 语义的一部分。它不能作为变量访问,当然也不能从数组本身的存储中访问。例如,它可能仅作为某些机器指令的数字操作数存在。

    • 它与[结构类型的变量]有何不同? “点”不也是一块内存的名字吗?

    是的,“点”也是一块内存的名称。在 C 规范的术语中,您的 array 和您的 point 在范围内都是左值。在这方面,数组与任何其他类型的对象并没有特别的不同。每个对象都可以看作是一个存储块,因此,每个变量的标识符都可以看作是一个存储块的名称。

    【讨论】:

      猜你喜欢
      • 2015-11-04
      • 2022-01-02
      • 2020-11-17
      • 2019-06-18
      • 2013-05-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多