“int array[int]”和“int *array”之间的混淆

6
int array[100];

int *array;

我对于 int array[100]int *array 的区别感到困惑。
基本上,当我使用 int array[100] 时(100 只是一个 int 类型的示例),我只是为 100 个整数在内存中保留了空间,但是我可以使用 int *array,并且没有指定该数组的任何类型和大小,但我仍然可以执行 array[9999] = 30,这仍然是有意义的。
那么这两者之间有什么区别呢?

我没有为这个数组指定任何类型或大小,但你通常需要这样做:int *array = malloc(sizeof(int)*10000)。在这里,10000就是数组的大小。如果您不让指针指向有效的内存空间,则array[9999]=30是未定义的行为,并且很可能会导致程序崩溃或产生其他错误/不可预测的结果。 - Blaze
首先,int *array 必须手动进行 malloc() 分配。 - Shark
哦,非常感谢。 - Joao Parente
我建议阅读c-faq中的第4、5、6和7节。 - pmg
@JoaoParente:您可以通过点击其分数下方的灰色复选标记来接受其中一个答案。 - chqrlie
7个回答

6

指针是一个指针,它指向其他地方(比如数组的第一个元素)。编译器没有关于它可能指向哪里或者指向的数据大小的任何信息。

数组是一组相同类型的连续元素的数组。编译器知道它的大小,因为它总是被指定的(尽管有时大小只是隐式指定的)。

数组可以被初始化,但不能被赋值。数组通常会衰减为指向它们第一个元素的指针。

数组衰减示例:

int array[10];

int *pointer = array;  // Here the symbol array decays to the expression &array[0]

// Now the variable pointer is pointing to the first element of array

数组无法自然地传递给函数。当您声明函数参数时,如int arr[],编译器将将其转换为int *arr

所有这些信息以及更多信息都应该在任何好的书籍、教程或课程中提供。


1
编译器并不总是知道数组的大小。例如,extern int array[]; 声明了一个在不同模块中定义或仅在同一模块中稍后定义的数组。大小是未知的,sizeof(array) 应该会产生一个错误。 - chqrlie

3
一个非技术性的解释:
指针的“内容”是指一个地址(可能有效,也可能无效)。而数组“有”一个地址(为了使数组存在必须是有效的)。
你可以将指针想象成一个信封 - 你可以在上面写任何地址,但如果你想把它寄到特定的地方,那个地址必须是正确的。
数组就像你的房子 - 它存在于某个地方,所以它“有”一个地址。适当寻址的物品会被送到那里。
简而言之:
指针“包含”一个地址。
数组“有”一个地址。
所以
int *array;

创建一个指针,其值是不确定的(它可以指向任何位置!)。

然后当你有了这个指针之后

array[9999] = 30;

你试图将第9999个int值从array所指向的位置设为30,但是因为你没有给array一个实际的值,所以你不知道它所指向的位置在哪里。这就是未定义行为。

2

区别在于当你使用int array[100]时,会在堆栈上分配100 * sizeof(int)的内存块;而当你使用int *array时,需要动态分配内存(例如使用malloc函数)来使用array变量。动态分配的内存位于上,而非堆栈


1
这个问题没有显示这些声明是出现在函数内部还是外部,这会影响内存的分配方式。而内存的提供方式取决于具体实现。此外,并不需要动态分配内存来使用指针。指针可以设置为指向其他内存,比如另一个数组中的内存。 - Eric Postpischil

2
int array[100] 表示一个变量 array,它将能够容纳100个int值,这个内存将从堆栈中分配。变量array将具有数组的基本地址,并为其分配内存。
但是,在int *array的情况下,由于您将其声明为本地变量,指针变量array将具有垃圾地址。因此,如果您执行array[9999],它可能会导致分段违规,因为您正在尝试访问程序外部的垃圾内存位置。
"最初的回答"

访问 array[9999] 不一定会导致段错误。这种行为在 C 标准中没有定义。该地址可能位于可访问内存内,或者代码可能会被优化以意想不到的方式转换。 - Eric Postpischil
是的,那是正确的。但在大多数情况下会发生崩溃。 - Amal John
1
答案可能会说访问数组越界经常会导致段错误,但它不应该让读者相信他们可以期待那种行为。答案应该包含真实的语句,不应该包含虚假陈述。 - Eric Postpischil
typedef结构体边缘{int dest; int cost; struct edge *next;} *EList; typedef EList Graph[100]; 所以如果我有这样的东西,我可以这样做: EList哈希;哈希[30]=NULL,因为当我声明EList哈希时,它会自动创建大小为100的哈希表,对吗? - Joao Parente

1

以下是一些你可能会发现有用的要点:

  • 通过 int arr[N],你可以指定一个类型为 int数组,它可以存储 N 个整数。要获取有关数组占用多少内存的信息,可以使用 sizeof 运算符。只需将数组中的项目数乘以类型的大小:N*sizeof(int)
  • 数组的名称指向数组中的第一个元素,例如,*arrarr[0] 相同,你可能会想知道为什么 a[5] == 5[a]
  • 未初始化的非静态存储期数组填充了不确定的值。
  • 如果你编写 int arr[] = {1, 2},则数组的大小由编译器计算,可以在运行时得知。
  • 访问不存在的元素可能会导致未定义的行为,这意味着任何事情都可能发生,在大多数情况下,你将获得垃圾值。

  • 通过 int *array,您可以指定类型为int的指针array
  • 除非分配了一个值,否则指针默认会指向一些垃圾地址。
  • 如果您根本没有分配内存或者没有完全分配内存或访问不存在的元素但尝试将指针用作数组,则会得到预期的未定义行为。
  • 在分配内存后(当指针不再需要时),应该释放内存。

0

int array[100]; 定义了一个 int 数组

int *array; 定义了一个指向 int指针。这个指针可以指向一个 int 变量,或者指向一个 int 数组的元素,也可能什么都不指向 (NULL),甚至是指向内存中一个任意的、有效或无效的地址,这种情况发生在它是未初始化的局部变量的情况下。称这个指针为array有点误导人,但通常用于命名函数参数,该参数确实指向实际的数组。编译器无法从指针值确定数组的大小(如果有)。

这里有一个地形隐喻:

  • 把数组看作是一条街道,上面有建筑物。它有GPS坐标(内存地址),有名称(但不总是),以及固定数量的建筑物(在给定时间内难以更改)。街道名称和建筑物编号共同指定了一个精确的建筑物。如果您指定的数字大于最后一个数字,则是无效地址。

  • 指针是一种非常不同的东西:将其视为地址标签。它是一张小纸片,可用于识别建筑物。如果它为空(空指针),则无用;如果您将其粘贴到信件上并发送该信件,则该信件将丢失和丢弃(未定义行为,但很容易判断其是否无效)。如果您在其上写入无效地址,则效果类似,但在交付失败之前可能会花费更多(未定义行为,并且难以进行测试)。

  • 如果一条街道被夷平(如果内存被释放),则先前编写的地址标签不会被修改,但它们不再指向任何有用的内容(如果您发送该信件,则是未定义行为,这是一种困难的情况)。如果稍后使用标签上的名称命名新街道,则可能会传递该信件,但可能不会按预期方式传递(再次是未定义行为,因为内存已被释放,并且某些其他分配的对象恰好位于相同的内存地址)。

  • 如果您将一个建筑物传递给函数,则通常不会挖掘它并运输它,而只需传递其街道地址(指向街道第n个建筑物的指针,&array[n])。如果您没有指定建筑物,只是命名了街道,则表示前往该街道的开头。同样,在C语言中将数组传递给函数时,函数接收到数组开头的指针,我们说数组退化为指针


0

int * array中不指定大小,array[9999] = 30可能会导致分段错误,因为它可能会导致访问不可访问的内存。

基本上,int * array指向随机位置。要访问第9999个元素,array必须指向具有足够空间的位置。但是语句int * array没有明确为此创建任何空间。


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