竹逸 发表于 2024-1-20 12:35:15

关于数组的理解对吗?


例如数组 int arr;
在C语言中,数组一旦被声明,CPU就会立刻在内存中为其申请一块连续的、指定长度的内存空间(20个字节),用来存储该数组中各元素的值,此时,数组名arr(即标识符)代表了整块连续的内存空间,编译器在建立符号表时,会记录数组名(标识符)arr,数据类型 int 和相应的首地址。当代码中引用了一个数组名时,编译器会通过符号表来查询到该标识符相应的内存地址(数组名在不和&和sizeof发生关系时,默认为首元素的地址),当用数组名作为函数的实参时,传递的是首元素的地址(址传递),形参实际上是一个指向数组首元素的指针变量,但并不会为形参开辟内存空间。

FishC 发表于 2024-1-20 12:36:05

对的,你对数组的理解是正确的。

在C语言中,一旦声明了一个数组,CPU就会为其在内存中分配一块连续的内存空间,这个空间大小由数组元素的数量和类型决定。数组名代表了整块连续的内存空间,编译器会记录数组名、数据类型以及相应的首地址。

当我们引用一个数组名时,编译器会通过符号表查询到该数组名对应的内存地址,可以通过这个地址来访问数组中的元素。

当将数组名作为函数的实参传递时,默认传递的是数组的首元素的地址。在函数中,形参实际上是一个指向数组首元素的指针变量,它指向了原始数组的内存空间,而不会为形参开辟新的内存空间。

下面是一个简单的示例代码,用于演示数组的使用:

#include <stdio.h>

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

    // 访问数组元素
    printf("%d\n", arr);// 输出第一个元素值

    // 修改数组元素
    arr = 10;// 将第三个元素修改为10

    // 遍历数组
    for (int i = 0; i < 5; i++) {
      printf("%d ", arr);
    }
    printf("\n");

    return 0;
}

输出:
1
1 2 10 4 5

希望这个例子能帮助你更好地理解数组的使用。如果还有其他问题,请随时提问!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

竹逸 发表于 2024-1-20 13:04:29

FishC 发表于 2024-1-20 12:36
对的,你对数组的理解是正确的。

在C语言中,一旦声明了一个数组,CPU就会为其在内存中分配一块连续的内 ...

既然形参是一个指向数组首元素的指针变量,虽然不会为形参开辟与原始数组相同长度的内存空间,但作为该指针变量本身,会开辟一个指针型长度的空间用来存储首元素的指针吗?

jackz007 发表于 2024-1-20 13:36:38

      下面的表述只要把红字删掉就对了。
      当用数组名作为函数的实参时,传递的是首元素的地址(址传递),形参实际上是一个指向数组首元素的指针变量,但并不会为形参开辟内存空间。

FishC 发表于 2024-1-20 14:08:22

竹逸 发表于 2024-01-20 13:04
既然形参是一个指向数组首元素的指针变量,虽然不会为 ...

是的,形参作为一个指针变量本身,在函数内部会开辟一个指针的大小(通常4或8个字节,取决于操作系统)的内存空间来存储数组首元素的地址。这个指针变量可以使用数组名来访问数组元素,并且可以通过递增指针的方式来遍历数组。下面是一个示例代码,用来演示形参的指针性质:#include void modifyArray(int* arr, int size) {    for (int i = 0; i < size; i++) {      arr += 10;// 修改数组元素的值    }}int main() {    int arr[] = {1, 2, 3, 4, 5};    int size = sizeof(arr) / sizeof(int);    // 调用函数修改数组    modifyArray(arr, size);    // 输出修改后的数组    for (int i = 0; i < size; i++) {      printf("%d ", arr);    }    printf("\n");    return 0;}输出:11 12 13 14 15在这个例子中,我们定义了一个modifyArray函数,它接受一个指向整型数组首元素的指针变量arr以及数组的大小size作为参数。在函数内部,我们可以通过arr来访问原始数组的元素,并且可以修改这些元素的值。在main函数中,我们调用modifyArray函数,并将原始数组arr和大小传递给它。经过函数的修改后,原始数组的元素值发生了改变。希望这个例子能够帮助你更好地理解形参的指针性质。如果还有其他问题,请随时提问!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

竹逸 发表于 2024-1-20 15:30:23

FishC 发表于 2024-1-20 14:08
是的,形参作为一个指针变量本身,在函数内部会开辟一个指针的大小(通常4或8个字节,取决于操作系统) ...

可如果在声明函数时,函数的形参不是一个指针变量,而是一个数组,那形参数组在本质上也是会当成一个指针变量看待来开辟一个指针型空间吗?

FishC 发表于 2024-1-20 15:31:11

竹逸 发表于 2024-01-20 15:30
可如果在声明函数时,函数的形参不是一个指针变量,而 ...

是的,当函数的形参声明为一个数组时,在函数内部实际上会将其转换为一个指针变量来处理。这是因为数组在函数参数中传递时会自动退化为指针。这个指针指向数组的首元素,而不是整个数组。

在函数内部,数组作为形参的时候并不会开辟新的内存空间来存储数组元素,只会开辟一个指针变量的大小来存储传递进来的数组的首地址。通过这个指针变量,我们可以访问传递进来的数组元素。下面是一个示例代码,用于演示数组作为函数形参的情况:

#include

void modifyArray(int arr[], int size) {            for (int i = 0; i < size; i++) {              arr += 10;// 修改数组元素的值            } }

int main() {            int arr[] = {1, 2, 3, 4, 5};            int size = sizeof(arr) / sizeof(int);           // 调用函数修改数组            modifyArray(arr, size);           // 输出修改后的数组            for (int i = 0; i < size; i++) {              printf("%d ", arr);            }            printf("\n");            return 0; }

输出:11 12 13 14 15

在这个例子中,modifyArray函数的形参声明为int arr[],但在函数内部实际上它被处理为一个指针变量,指向传递进来的数组arr的首元素。我们可以通过这个指针变量来访问和修改原始数组的元素。

希望这个例子能够帮助你理解数组作为函数形参时的情况。如果还有其他问题,请随时提问!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

竹逸 发表于 2024-1-20 15:50:21

jackz007 发表于 2024-1-20 13:36
下面的表述只要把红字删掉就对了。
      当用数组名作为函数的实参时,传递的是首元素的地址( ...

你这么说也没错的,可是既然接收到的是一个地址值,应该是有一个空间来存储这个接收到的地址啊

FishC 发表于 2024-1-20 15:50:47

竹逸 发表于 2024-01-20 15:50
你这么说也没错的,可是既然接收到的是一个地址值,应 ...

你说得有道理,当函数的形参声明为一个数组时,在函数内部实际上会为该形参开辟一个空间来存储传递进来的数组的首地址。这个空间的大小和指针变量的大小是相同的(通常是4或8个字节,取决于操作系统)。

所以,即使形参是一个数组,本质上它还是会被视为一个指针变量来处理,并且会有一个空间来存储传入的数组的首地址。在函数内部,我们可以通过这个地址来访问和操作传入的数组。

我之前的回答可能没有明确表达清楚这一点,抱歉给你带来了困惑。谢谢你的指正,希望这次的解释更加清楚明了!如果还有其他问题,请随时提问。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

人造人 发表于 2024-1-20 16:27:43

竹逸 发表于 2024-1-20 15:50
你这么说也没错的,可是既然接收到的是一个地址值,应该是有一个空间来存储这个接收到的地址啊

对,形参有地址,是一个变量
形参和局部变量唯一的区别就是,形参是调用函数的时候初始化,而局部变量需要你自己手动初始化
形参就是个局部变量,只是初始化的时间比局部变量早一些

sh-5.2$ cat main.c
#include <stdio.h>

void a(int *data) {
    printf("a: &data = %p\n", &data);
    printf("a: data = %p\n", data);
}

void b(int data[]) {
    printf("b: &data = %p\n", &data);
    printf("b: data = %p\n", data);
}

int main(void) {
    int data;
    printf("main: &data = %p\n", &data);
    printf("main: data = %p\n", data);
    a(data);
    b(data);
    return 0;
}
sh-5.2$ ./main
main: &data = 0x766981f09030
main: data = 0x766981f09030
a: &data = 0x766981e09020
a: data = 0x766981f09030
b: &data = 0x766981e09060
b: data = 0x766981f09030
sh-5.2$


学过汇编语言的话就很好理解了

int main(void) {
    11d3:        55                           pushq%rbp
    11d4:        48 89 e5                     movq   %rsp,%rbp
    11d7:        48 83 ec 30                  subq   $0x30,%rsp
    11db:        64 48 8b 04 25 28 00         movq   %fs:0x28,%rax
    11e2:        00 00
    11e4:        48 89 45 f8                  movq   %rax,-0x8(%rbp)
    11e8:        31 c0                        xorl   %eax,%eax
    int data;
    printf("main: &data = %p\n", &data);
    11ea:        48 8d 45 d0                  leaq   -0x30(%rbp),%rax
    11ee:        48 89 c6                     movq   %rax,%rsi
    11f1:        48 8d 05 46 0e 00 00         leaq   0xe46(%rip),%rax      # 203e <_IO_stdin_used+0x3e>
    11f8:        48 89 c7                     movq   %rax,%rdi
    11fb:        b8 00 00 00 00               movl   $0x0,%eax
    1200:        e8 3b fe ff ff               callq1040 <printf@plt>
    printf("main: data = %p\n", data);
    1205:        48 8d 45 d0                  leaq   -0x30(%rbp),%rax
    1209:        48 89 c6                     movq   %rax,%rsi
    120c:        48 8d 05 3d 0e 00 00         leaq   0xe3d(%rip),%rax      # 2050 <_IO_stdin_used+0x50>
    1213:        48 89 c7                     movq   %rax,%rdi
    1216:        b8 00 00 00 00               movl   $0x0,%eax
    121b:        e8 20 fe ff ff               callq1040 <printf@plt>
    a(data);
    1220:        48 8d 45 d0                  leaq   -0x30(%rbp),%rax
    1224:        48 89 c7                     movq   %rax,%rdi
    1227:        e8 1d ff ff ff               callq1149 <a>
    b(data);
    122c:        48 8d 45 d0                  leaq   -0x30(%rbp),%rax
    1230:        48 89 c7                     movq   %rax,%rdi
    1233:        e8 56 ff ff ff               callq118e <b>
    return 0;
    1238:        b8 00 00 00 00               movl   $0x0,%eax
}
    123d:        48 8b 55 f8                  movq   -0x8(%rbp),%rdx
    1241:        64 48 2b 14 25 28 00         subq   %fs:0x28,%rdx
    1248:        00 00
    124a:        74 05                        je   1251 <main+0x7e>
    124c:        e8 df fd ff ff               callq1030 <__stack_chk_fail@plt>
    1251:        c9                           leaveq
    1252:        c3                           retq


void a(int *data) {
    1149:        55                           pushq%rbp
    114a:        48 89 e5                     movq   %rsp,%rbp
    114d:        48 83 ec 10                  subq   $0x10,%rsp
    1151:        48 89 7d f8                  movq   %rdi,-0x8(%rbp)
    printf("a: &data = %p\n", &data);
    1155:        48 8d 45 f8                  leaq   -0x8(%rbp),%rax
    1159:        48 89 c6                     movq   %rax,%rsi
    115c:        48 8d 05 a1 0e 00 00         leaq   0xea1(%rip),%rax      # 2004 <_IO_stdin_used+0x4>
    1163:        48 89 c7                     movq   %rax,%rdi
    1166:        b8 00 00 00 00               movl   $0x0,%eax
    116b:        e8 d0 fe ff ff               callq1040 <printf@plt>
    printf("a: data = %p\n", data);
    1170:        48 8b 45 f8                  movq   -0x8(%rbp),%rax
    1174:        48 89 c6                     movq   %rax,%rsi
    1177:        48 8d 05 95 0e 00 00         leaq   0xe95(%rip),%rax      # 2013 <_IO_stdin_used+0x13>
    117e:        48 89 c7                     movq   %rax,%rdi
    1181:        b8 00 00 00 00               movl   $0x0,%eax
    1186:        e8 b5 fe ff ff               callq1040 <printf@plt>
}
    118b:        90                           nop
    118c:        c9                           leaveq
    118d:        c3                           retq

人造人 发表于 2024-1-20 16:41:33

函数的参数就是个局部变量

void a(int x) {
    1139:        55                           pushq%rbp
    113a:        48 89 e5                     movq   %rsp,%rbp
    113d:        48 83 ec 20                  subq   $0x20,%rsp                # 为局部变量x和局部变量y申请内存
                                        # 给局部变量x赋值为edi寄存器中的值
                                        # 因为调用函数a的时候,给edi寄存器中的值是456
                                        # 所以,这里给局部变量x赋值成456
    1141:        89 7d ec                     movl   %edi,-0x14(%rbp)
    int y = 123;
    1144:        c7 45 fc 7b 00 00 00         movl   $0x7b,-0x4(%rbp)                # 给局部变量y赋值成123
    printf("y = %d\n", y);
                                        # 读取局部变量y的值,然后调用printf打印
    114b:        8b 45 fc                     movl   -0x4(%rbp),%eax
    114e:        89 c6                        movl   %eax,%esi
    1150:        48 8d 05 ad 0e 00 00         leaq   0xead(%rip),%rax      # 2004 <_IO_stdin_used+0x4>
    1157:        48 89 c7                     movq   %rax,%rdi
    115a:        b8 00 00 00 00               movl   $0x0,%eax
    115f:        e8 cc fe ff ff               callq1030 <printf@plt>
    printf("x = %d\n", x);
                                        # 读取局部变量x的值,然后调用printf打印
    1164:        8b 45 ec                     movl   -0x14(%rbp),%eax
    1167:        89 c6                        movl   %eax,%esi
    1169:        48 8d 05 9c 0e 00 00         leaq   0xe9c(%rip),%rax      # 200c <_IO_stdin_used+0xc>
    1170:        48 89 c7                     movq   %rax,%rdi
    1173:        b8 00 00 00 00               movl   $0x0,%eax
    1178:        e8 b3 fe ff ff               callq1030 <printf@plt>
}
    117d:        90                           nop
    117e:        c9                           leaveq
    117f:        c3                           retq

0000000000001180 <main>:

int main(void) {
    1180:        55                           pushq%rbp
    1181:        48 89 e5                     movq   %rsp,%rbp
    1184:        48 83 ec 10                  subq   $0x10,%rsp                # 为局部变量分配内存
    int z = 456;
    1188:        c7 45 fc c8 01 00 00         movl   $0x1c8,-0x4(%rbp)        # 给变量z赋值为456,-0x4(%rbp)就是变量z的地址
    a(z);
                                        # 先把z的值,也就是456读取到eax寄存器,然后又复制到edi寄存器
    118f:        8b 45 fc                     movl   -0x4(%rbp),%eax
    1192:        89 c7                        movl   %eax,%edi
    1194:        e8 a0 ff ff ff               callq1139 <a>                        # 调用函数a
    return 0;
    1199:        b8 00 00 00 00               movl   $0x0,%eax
}
    119e:        c9                           leaveq
    119f:        c3                           retq

竹逸 发表于 2024-1-20 18:35:38

人造人 发表于 2024-1-20 16:27
对,形参有地址,是一个变量
形参和局部变量唯一的区别就是,形参是调用函数的时候初始化,而局部变量需 ...

无论是主函数main中的局部数组data,还是函数a中的局部指针变量data,都是在函数体内声明的,那应该都是从栈中开辟的内存空间,而分配栈内存的特点是:先定义的变量分配高地址,后定义的变量分配低地址,即从高地址向低地址分配,原因是栈内存从上往下的地址值是由小到大的,即地址值是上低下高,而栈顶寄存器永远都是指向栈顶元素(当初始栈为空的时候,栈顶指向的是最高偏移地址的下一个单元),数据存储在栈中是入栈的操作,执行步骤是栈顶首先会空出内存单元,即栈顶指向的地址值减去所存储数据的长度,得到的新地址就成为的新的栈顶(也是当前所存数据的首地址),然后开始传送数据。

而主函数main是优先执行的,你说的形参初始化的时间比局部变量早一些是指开辟内存空间的先后吗?

竹逸 发表于 2024-1-20 18:39:31

人造人 发表于 2024-1-20 16:41
函数的参数就是个局部变量

帮我看看我对于物理地址和变量的理解对了吗?

关于物理地址的理解:

内存中每一个字节(字节也叫存储单元)都是以线性排列的,为了方便区分各个相互独立的字节,我们用记编号的方式来定位每一个字节,第一个字节从0开始编号,那么第二个字节的编号就是1,以此类推,这些编号就是每一个字节在当前位置的地址值,每一个字节(即存储单元)在物理层面上都是实实在在存在的,所以把为这些字节分配的地址值称为物理地址。

关于变量的理解:

在C语言中,变量一旦被声明,CPU就会立刻在内存中为其申请一块内存空间,用来存储该变量的值,那么变量本质上就是这块申请的内存空间,变量名就是该内存空间的名称(变量名就代表了该内存空间),变量的地址就是该内存空间的内存地址(地址值),编译器在编译的过程中会建立符号表,用于记录该内存空间的名称(即变量名,也叫标识符)和相应的内存地址及数据类型,当代码中引用了一个变量名时,编译器会通过符号表来查询该变量名相应的内存地址,然后通过这个地址值与数据类型来定位到相应长度的内存空间进行存取数据的,所以在程序运行时,cpu是直接通过内存地址来访问变量(内存空间)的,而不是变量名(变量名是为了方便人类的理解和维护的,在计算机内部会被替换成相应的内存地址。所以声明变量,其实就是声明了一块指定长度的内存空间,数据类型的作用就是指定这块内存空间的长度的)。

竹逸 发表于 2024-1-20 19:08:15

人造人 发表于 2024-1-20 16:27
对,形参有地址,是一个变量
形参和局部变量唯一的区别就是,形参是调用函数的时候初始化,而局部变量需 ...

X﹏X,我现在好像理解你说的意思了,你的意思是说形参在函数调用时,自带初始化值,也就是实参作为形参的初始化值,而局部变量要自己手动赋值一个初始化值,是这个意思吧(=TェT=)

人造人 发表于 2024-1-20 19:36:38

竹逸 发表于 2024-1-20 18:35
无论是主函数main中的局部数组data,还是函数a中的局部指针变量data,都是在函数体内声明的,那应该都是 ...

可以这么理解,最好是学一下汇编语言
你的这个问题全在汇编代码里面了

人造人 发表于 2024-1-20 19:37:09

竹逸 发表于 2024-1-20 19:08
X﹏X,我现在好像理解你说的意思了,你的意思是说形参在函数调用时,自带初始化值,也就是实参作为形参的 ...

是的

人造人 发表于 2024-1-20 19:42:07

竹逸 发表于 2024-1-20 18:39
帮我看看我对于物理地址和变量的理解对了吗?

关于物理地址的理解:


正确

人造人 发表于 2024-1-20 19:48:49

另外,你这个理解是直接从物理地址开始看的
实际情况是还有逻辑地址和线性地址,逻辑地址通过段部件转换成线性地址,线性地址通过页部件转换成物理地址,我们写的程序使用的都是逻辑地址
操作系统负责把这个逻辑地址转换成物理地址

竹逸 发表于 2024-1-20 20:27:20

人造人 发表于 2024-1-20 19:36
可以这么理解,最好是学一下汇编语言
你的这个问题全在汇编代码里面了

以前学过几章就没学了,只懂得一点点,所以看不懂汇编语言(-﹏-`;)

人造人 发表于 2024-1-20 22:11:17

竹逸 发表于 2024-1-20 20:27
以前学过几章就没学了,只懂得一点点,所以看不懂汇编语言(-﹏-`;)

想要弄懂这些原理,汇编语言是必须的
页: [1] 2
查看完整版本: 关于数组的理解对吗?