【问题标题】:About pointer of a pointer array关于指针数组的指针
【发布时间】:2018-08-16 04:39:15
【问题描述】:

我正在观看来自 Bucky 在 YT 上的关于堆的教程视频。

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <math.h>

int n, howMany;
int total;
float average = 0.0;
int * pointsArray;

printf("How many numbers do you want to average?\n");
scanf(" %d", &howMany);

pointsArray = (int *) malloc(howMany * sizeof(int));

printf("Enter numbers! \n");

for(n = 0; n<howMany; n++){
scanf(" %d", &pointsArray[n]);
total += pointsArray[n];
}
average = (float)total /(float)howMany;
printf("average is %f", average);
free(pointsArray);

return 0;
}

我不明白在上述 for 循环中,pointer-pointsArray 是如何从指针变为指针数组的。 我对变量中的右值和 Ivalue 有了解。如果您可以在答案中包含这些概念,我将不胜感激。

  1. pointsArray 在哪一行变成了指针数组? pointsArray = (int *) malloc(howMany * sizeof(int));应该只增加 pointsArray 的内存大小。
  2. 为什么编译器看到 &pointsArray[n] 就知道如何在数组中输入值?

  3. 我能够确定我的问题应该在于我混淆了数组和变成数组的指针。我可以有一个明确的定义,以便我可以将它们分开吗?

非常感谢。如果我的问题不清楚,请告诉我。我会努力改进的。

【问题讨论】:

  • pointsArray 是指向int 的指针,只有且始终如此。从malloc 行开始,它指向数组中的第一项,但它不会变成指针数组,或指向数组的指针,或类似的东西。它的类型仍然是int *
  • (还请注意,此代码是可利用的。在将输入传递给malloc 之前,您必须始终检查输入。)

标签: c arrays pointers


【解决方案1】:

我不明白 pointer-pointsArray 如何从指针变为 上面 for 循环中的指针数组。我对右值和 变量中的值。如果你能包括这些,我将不胜感激 答案中的概念。

  1. pointsArray 在哪一行变成了指针数组? pointsArray = (int *) malloc(howMany * sizeof(int));应该只增加内存 pointsArray 的大小。

首先,让我们从基础开始。指针不是数组,数组也不是指针,它们在 C 中是两种不同的类型。数组类型在访问时转换为指向数组第一个元素的指针,如C11 Standard - 6.3.2.1 Lvalues, arrays, and function designators(p3)中所提供的@

int * pointsArray;
...
pointsArray = (int *) malloc(howMany * sizeof(int));

pointsArray 是什么? (它是一个指针,用于键入int)。

那么,什么是指针?

指针只是一个普通变量,它保存着其他东西的地址作为它的值。换句话说,一个指针指向可以找到其他东西的地址。您通常会想到一个保存立即值的变量,例如int a = 5;,而指针只会保存5 存储在内存中的地址,例如int *b = &amp;a;b 声明为指向int 类型的指针,并将其值初始化为5 存储在内存中的地址(例如b 指向 a -- 其中5 当前已存储)。无论指针指向什么类型的对象,它的工作方式都是相同的。

无论类型如何,指针运算对所有指针都一样,因为指针的type 控制指针运算,例如使用char * 指针,pointer+1 指向pointer 之后的下一个字节。对于int * 指针(普通的 4 字节整数),pointer+1 将指向在pointer 之后 4 字节偏移处的下一个 整数。 (所以一个指针,只是一个指针....算术由type自动处理)

  1. pointsArray 在哪一行变成了指针数组? pointsArray = (int *) malloc(howMany * sizeof(int));应该只增加内存 pointsArray 的大小。

回想一下,“指针不是数组,数组也不是指针。”数组声明为:

type name[CONST];      /* declares an array of type with CONST elements */
type name[var];        /* declares a VLA C99+ with var no. of elements */
type name[] = { 1, 2, 3 }; /* declares & initializes an array of 3 elements */

对比:

type *name;            /* declares a pointer to type */

对于具有恒定元素数或具有初始化列表的数组,为该数组提供自动存储。可变长度数组 (VLA) 的存储类似,但在另一个段中(通常在 .bss [由符号表示的块] 中)。访问时(除非“它是sizeof 运算符、_Alignof 运算符或一元&amp; 运算符的操作数,或者是用于初始化数组的字符串文字”)数组被转换为指向类型的指针,它指向数组对象的初始元素,而不是左值。 6.3.2.1(p3)(你会经常看到短语an “数组衰减为指针”——指的是这种转换)

注意:它不能反向工作,指针永远不会转换为数组)

声明type *name;name 声明为指向type 的指针。它是未初始化的,它拥有一个不确定的地址。任何访问或以其他方式使用未初始化指针的尝试都会调用未定义行为(通常会导致分段错误)。在您可以访问内存之前,指针必须拥有一个有效的地址(例如,它必须指向有效的存储空间)。为指针提供有效存储的最简单方法是动态分配内存块并将块的起始地址分配给指针。这是在您的代码中完成的:

pointsArray = (int *) malloc(howMany * sizeof(int));

(注:malloc的return不用投,没必要。见:Do I cast the result of malloc?

更好的方法是:

pointsArray = malloc (howMany * sizeof *pointsArray);

pointsArrayint* 类型,所以 *pointsArrayint 类型。如果您使用取消引用的指针作为您的类型大小,您将消除类型大小错误的可能性)

在上面,malloc 保留了一个有效的内存块,保证其大小至少为howMany * sizeof *pointsArray 字节。内存块的起始地址分配给pointsArray(例如,pointsArray 现在指向该块)内存已分配存储持续时间并且在您free() 它之前一直有效,或者程序结束。您有责任保留一个指针,该指针包含内存块的起始地址,以便在不再需要时可以将其释放。

您现在最多可以在pointsArray 指向的内存块中存储howMany 整数。要访问整数的第一个地址,您可以使用 指针表示法 使用 *(pointArray + 0)(即 *pointsArray)或使用 数组索引表示法 使用 pointsArray[0] >。 (关键是要了解[] 的作用与*(name + offset) 中的'*' 相同。所以要访问第二个元素,您可以使用*(pointsArray + 1)pointsArray[1] 等。虽然您可以将 数组索引 表示法与指针一起使用——这不会使指针成为数组或将其转换为数组。

  1. 当编译器看到&amp;pointsArray[n]时,为什么编译器知道如何在数组中输入值?

这与scanf 的要求有关,而不是pointsArray 的任何有趣之处。想想看。从man 3 scanf() 使用%d 格式转换说明符时,scanf

Matches an optionally signed decimal integer; the next pointer 
must be a pointer to int.

因此,如果您想将一个整数读入使用int a; 声明的变量中,那么您对scanf 的使用将是:

if (scanf ("%d", &a) == 1) {
    /* you have a good integer */
}
else {
    /* a matching or input failure occurred */
}

对于将由scanf 放置在a 中的整数值,您必须提供一个指向a 的指针。将值放入整数 pointsArray[n] 时也没有什么不同。你如何提供一个指针(地址)pointsArray[n]? (答案:你在它前面加上运营商的一元'&amp;'地址——&amp;pointsArray[n]

希望这也能照顾到你的 3 号。如果您还有其他问题,请仔细查看并告诉我。

【讨论】:

  • 我还在消化。关于“声明类型 *name; 将名称声明为一个点......任何访问或以其他方式使用未初始化指针的尝试都会调用未定义行为(经常导致分段错误)”。我试过 int * pa; printf("\n %p 代表 &pa,%p 代表 pa", &pa,pa);结果是找到了 &pa(pointer of pointer) 和 pa(pointer) 的内存地址,尽管指针的值未初始化。它不包含不确定的地址。请澄清,谢谢!
  • 哈!声明 int *pa; 声明了一个指针。变量pa 本身有一个有效的地址,但是它作为其值保存的地址是indeterminate。所以%p&amp;pa 很好,您只是打印指针的地址,而不是指针持有的地址 作为其值。 %ppa 同样没有问题,因为没有尝试访问该地址处的值。它只是打印不确定的地址(无论当时内存中的垃圾值是什么)。再次使用&amp;pa.papa.pa 是某个地址(不确定与否)&amp;pa.ap 是持有它的地址。不可访问。
【解决方案2】:

数组是连续的内存块,变量保存内存的开始。

int* pointsArray
pointsArray = malloc(sizeof(*pointsArray) * howMany);

现在pointsArray 持有(指向)一些内存块,在这种情况下由malloc 返回,您通常可以使用数组注释来访问内存条目。

您的指针指向 int 类型的内存(我假设)在您的机器上每个 4 bytes。当您尝试访问内存 pointsArray[n] 时,您正在访问内存位置:

pointsArray[0] = pointsArray + 4 * 0;
pointsArray[1] = pointsArray + 4 * 1;
pointsArray[n] = pointsArray + 4 * n;

如果我们看起来非常通用,每个索引都有sizeof(type) 的偏移量,在您的情况下,类型是int,即4 字节。

pointsArray[n] = pointsArray + sizeof(*pointsArray) * n

请注意,强制返回 malloc 不是一个好主意,将sizeof(...) 部分添加到大小计算的开头会更好、更安全。

Do I cast the result of malloc?

【讨论】:

  • 我认为写pointsArray[1] = pointsArray + 4 * 1;很危险,如果没有仔细阅读文本,读者可能会认为这在C语言中是正确的。
  • @Gerhardh 也许,但你已经对自己回答:如果他们没有仔细阅读文本
  • 或者如果他们不完全理解文本和区别。这可能会匹配大约 100% 的普通初学者。
  • 很清楚,我所说的内存位置sizeof(type)字节的偏移量。
【解决方案3】:

pointsArray 在哪一行变成了指针数组?
pointsArray = (int *) malloc(howMany * sizeof(int)); 应该只会增加pointsArray 的内存大小。

你是对的。它不会变成指针数组。

当编译器看到&pointsArray[n]时,为什么编译器知道如何在数组中输入值?

编译器与此无关。该代码只是将输入值读取到sizeof(int)(4 个字节)到pointsArray 指向的内存中。在for 循环的每次迭代中,它都会移动四个字节并将输入值写入该内存位置。

我能够确定我的问题应该在于我混淆了数组和变成数组的指针。我可以有一个明确的定义,以便我可以将它们分开吗?

由于指针保存地址位置的值,因此包括数组在内的任何类型的地址都是指针。所以指针算法可以应用在它上面。

【讨论】:

    【解决方案4】:

    数组只是一个连续的内存位置,所以如果你有第一个元素的地址,你可以通过添加到第一个地址来访问其他元素。

    所以如果你有一个指针指向数组的第一个元素,就像这里

    pointsArray = (int *) malloc(howMany * sizeof(int));

    pointsArray 指向第一个元素。

    现在要访问其他元素,您可以使用 pointsArray[n] 转换为 pointsArray + sizeof(*pointsArray) * n(第 n 个元素的地址)来访问其他元素。

    【讨论】:

    • 准确地说,它以字节为单位转换为pointsArray[n] == pointsArray + sizeof(*pointsArray) * n
    • @tilz0R:这是一种不好的描述方式,因为在 C 中评估该表达式会给出错误的结果。如果你要描述底层的地址算术,你应该使用与代码符号不同的排版,并清楚这一点。使用传统的数学符号会更好,例如,如果 s 个字节对象的数组从地址为 p 的字节开始,则对象的地址为 n数组的 i> 位于字节 p + s × n.
    • 我不完全理解s x n的含义。你能澄清一下吗? n 是数组的元素。通过一个 s 字节对象的数组从地址为 p 的字节开始,这是否意味着每个对象都应该有 s 字节?能否请您提供一些有关此主题的关键字,以便我进一步调查?谢谢。
    • s 是第一个。分配给数组中单个元素的字节数,n 是它在数组中的位置,从 0 开始。
    • 知道了。因为 C 中的数组是一系列内存。因此,从地址 p 按顺序添加每个元素对象的字节将找到其余元素。谢谢,伙计。
    【解决方案5】:

    指针只是内存中特定单元的地址(索引),它知道其类型的基本长度,例如int32_t *p = 0x12345678 是字节 0x12345678 处的地址,长度为 4 个字节。所以当我们做p+1时,结果是0x1234567c。注意Pointer 本身是一种长度为 4 或 8 个字节的类型,具体取决于您的盒子。

    对于你的问题,

    1. pointsArray 始终是且仅是 int 的指针。它不知道有关数组的任何信息,例如数组的长度。在pointsArray = (int *) malloc(howMany * sizeof(int)) 行之后,pointsArray 有一个有效值(地址),它是分配给 int 数组的内存地址。 (pointsArray 不知道数组的长度)。
    2. 对于&amp;pointsArray[n],编译器只写入地址pointsArray + n的内存。
    3. 数组的名称与 const 指针非常相似(我记得的唯一区别是,当您将 sizeof 用于数组名称时,您可以获得数组的大小,而对于 const 指针,您会得到指针的大小)。在 C 中,我不喜欢使用 array 的概念,只是将其视为某种类型的 const 指针。

    【讨论】:

    • @EricPostpischil 谢谢,是的,它们有些不同。我更新了一点
    猜你喜欢
    • 2021-01-11
    • 1970-01-01
    • 2013-11-16
    • 1970-01-01
    • 1970-01-01
    • 2017-05-13
    • 1970-01-01
    • 2016-07-30
    相关资源
    最近更新 更多