【问题标题】:Why we don't need to de-reference pointer value in case of malloc array?为什么我们不需要在 malloc 数组的情况下取消引用指针值?
【发布时间】:2020-11-08 14:31:54
【问题描述】:

我在试图理解 C 编译器如何以隐蔽的方式解释字符数组、字符串、整数和数组时遇到了困难。并感谢您帮助我。甚至许多 reddit 用户也指出,C 中的整个指针对于它如何准确地解释命令有点可疑。 所以,我想通过一个例子来理解malloc。但在此之前,这是我所理解的。 说*p 是一个变量。然后 p 保存它指向的地址。 并且*p 引用地址所持有的值。 在动态分配的情况下, 当我这样做时

int *p = (int *)malloc(sizeof(int)*5); //say we want 5 locations //

并在用户输入所有 5 个值时手动循环,我输入的值如下

scanf("%d", p+i) // as p already holds address, I get that we don't have to provide the & . And i being the iterator.. //

然后用另一个循环打印相同的内容,这就是发生的事情,我不知道为什么。

假设用户输入55,66,77,88,99

当我使用此代码打印时,

printf("%d", *p+i); // De-referencing the values in location p by using asterics, + the value of iteration,i //

我得到的值和 55,56,57,58,59

一样疯狂

因此,在互联网的一些帮助下,我尝试了这样的 printf 代码,它的工作原理就像一个魅力,但是 我什至不必取消引用。为什么会如此混乱?

printf("%d", p[i]); // No asterics used. How does the compiler know I want the value and not the address? as p only should hold the addresses, and *p should give us the values in those addresses //

另外,如果我这样做printf("%d", *p++); 然后它也有效。 为什么我不了解 C 的工作原理?

同样,如果我尝试这样做但这次使用scanf("%d", p[i]);,则会出现语法错误。 我是说为什么?

谢谢...

【问题讨论】:

    标签: arrays c pointers memory-management malloc


    【解决方案1】:
    int *p = ....
    

    这里,pint * 类型的变量,也就是说,变量p 是一个指针,可以保存int 类型的地址。

    p 指向的已分配和初始化内存的内存视图如下所示:

    p ______
           |
          \|/
         --------------------------
         | 55 | 66 | 77 | 88 | 99 |
         --------------------------
    

    你对scanf("%d", p+i);的说法是正确的-
    // as p already holds address, I get that we don't have to provide the &.....

    问题一:

    当我使用此代码打印时,

    printf("%d", *p+i); // De-referencing the values in location p by using asterics, + the value of iteration,i //
    

    我得到的值和 55,56,57,58,59 一样疯狂

    一元*(间接)运算符的precedence高于二元+运算符。
    因此,表达式 *p+i 将被评估为 - (*p)+i

    请注意,运算符优先级是将不同类型的运算符及其操作数分组的优先级。

    当你在循环体中有printf("%d", *p+i); 语句,它将给定的分配数组从i = 0 迭代到i < 5 -
    在第一次迭代中:(i = 0 和指针 p 指向数组的第一个元素 55):

    (*p) + 0  ->  55 + 0  ->  55
    

    在第二次迭代中:(i = 1 和指针 p 指向数组的第一个元素 55):

    (*p) + 1  ->  55 + 1  ->  56
    

    .....等等
    .....

    在第五次迭代中:(i = 4 和指针 p 指向数组的第一个元素 55):

    (*p) + 4  ->  55 + 4  ->  59
    

    因此你得到输出 - 55,56,57,58,59。

    要获得预期的输出,请将运算符与操作数显式分组 - *(p + i)

    这样,首先将i 的值添加到指针中,然后取消引用结果指针(地址)。

    问题二:

    我尝试了这样的 printf 代码,它的工作原理非常棒,但我什至不必取消引用。这到底是怎么回事?

    printf("%d", p[i]); // No asterics used. How does the compiler know I want the value and not the address? as p only should hold the addresses, and *p should give us the values in those addresses //
    

    来自 C 标准#6.5.2.1:

    下标运算符[]的定义是E1[E2]等同于(*((E1)+(E2)))。

    通过这个下标运算符的定义-

    p[i] -> *((p) + (i)) -> *(p + i)
    

    这意味着,p[i]*(p + i) 是等价的。
    我已经在上面的答案中向您展示了 - 为什么*(p + i) 会给出预期的输出。
    因此,当使用 p[i] 时,您会得到预期的输出。

    问题三:

    另外,如果我这样做 printf("%d", *p++); 那么它也可以工作。为什么我不了解 C 的工作原理?

    再次查看运算符优先级表,您会发现++(后缀增量)运算符的优先级高于一元*(间接)运算符。 因此,表达式*p++ 将被评估为*(p++)

    后自增运算符将操作数的值增加1,但表达式的值是自增操作之前操作数的原始值。

    当您在循环体中有 printf("%d", *p++); 语句时,它正在迭代给定的分配数组 -
    在第一次迭代中:指针p 指向数组的第一个元素,即0th 索引处的元素。

    *(p++) -> p will be incremented but the value of p in the expression will be its value prior to the increment operation which is location of element at `0`th index
           -> *(0th location address) -> 55
    

    在第二次迭代中:指针p 指向数组的第二个元素,即1st 索引处的元素。

    *(p++) -> p will be incremented but the value of p in the expression will be its value prior to the increment operation which is location of element at `1`st index
           -> *(1st location address) -> 66
    

    ……等等
    ......

    在第五次迭代中:指针p 指向数组的第五个元素,即4th 索引处的元素。

    *(p++) -> p will be incremented but the value of p in the expression will be its value prior to the increment operation which is location of element at `4`th index
           -> *(4th location address) -> 99
    

    p 现在指向数组末尾之后的一个元素。

    您将获得预期的输出 - 55,66,77,88,99。

    问题四:

    同样,如果我尝试这样做但这次使用scanf("%d", p[i]);,则会出现语法错误。我是说为什么?

    在答案中重新审视上述下标运算符的定义,并回想一下您知道scanf() 期望变量(指针)的地址作为参数的事实。

    p[i] -> *((p) + (i)) -> *(p + i)
    

    *(p + i) ==> 取消引用指针p + i,这将在该位置给出值。
    所以,p[i] 不是数组的ith 位置的地址,而是数组的ith 位置的值。因此,将p[i] 作为参数传递给scanf() 时会出错。

    要获取ith位置的地址,只需在p[i]之前添加&操作-

    scanf("%d", &p[i]);
    

    这将按预期工作。又是一头雾水,为什么?

    检查这个 -

    &p[i] -> &(p[i]) -> &(*((p) + (i)) -> ((p) + (i)) -> p + i
              |                |
      Precedence of operator   |
      [] is higher than        |
      & operator               |
                               |
          The operator & is used to get the address and the operator * is used for dereferencing.
          These operators cancel the effect of each other when used one after another. 
    

    因此,声明

    scanf("%d", &p[i]);
    

    等价于这个语句

    scanf("%d", p + i);
    

    你很清楚为什么p + iscanf() 中作为参数给出时可以正常工作。

    如果您有任何其他问题或困惑,请告诉我。

    【讨论】:

    • 非常感谢。我很清楚,我给出的阴暗评论是基于编译器选择的优先级。谢谢。我现在知道的更多了。
    【解决方案2】:

    表达式p[i] 完全等价于*(p + i) - 给定起始地址p,计算该地址之后的i'th 对象(不是字节!)的地址并取消引用结果。

    您不要使用 * 运算符,因为在下标操作中存在隐式取消引用。

    *p + i 等价于编写p[0] + i - 你取消引用p 并将i 的值添加到结果中,这就是为什么你在期待55, 66, 77, 88, 99 时得到序列55, 56, 57, 58, 59

    如果您使用数组表示法,生活会更轻松 - 当您想要序列中的第 i'th 个对象时,只需编写 p[i]

    【讨论】:

      【解决方案3】:

      当我使用此代码打印时 printf("%d", *p+i); ... 我得到的值与 55,56,57,58,59 一样疯狂

      当您使用*p + i 时,首先取消引用p(获得第一个数组元素的值,即55),然后将i 添加到值55*p + i 等于 (*p) + i

      这就是为什么你会得到以下输出:55,56,57,58,59 而不是55,66,77,88,@ 987654343@

      您只需将i 添加到每次迭代中的第一个元素的值,而永远不会访问以下 4 个数组元素。

      我尝试了这样的 printf 代码,它的工作原理非常棒,但我什至不必取消引用......printf("%d", p[i]);

      当您使用p[i] 时,您会在数组的ith 元素处获得值,因为它在取消引用之前会偏移指针本身。它等于*(p + i)

      另外,如果我这样做printf("%d", *p++);,那么它也可以工作。

      当您使用*p++ 时,p 实际被取消引用,*p 处的值被获得,但随后p 增加。表示在下一次迭代中p 指向下一个数组元素。

      循环完成后,p 指向数组的后一位置。


      如果我尝试做同样的事情,但这次使用scanf("%d", p[i]);,那么语法错误。我的意思是为什么?

      对于scanf(),情况有些不同。

      当您使用 scanf("%d", p[i]); 时,p[i] 等于 *(p + i),因为它也在 printf() 调用中,但是当您尝试取消引用并获得 int 对象而不是指向 int 的指针时,这是%dscanf()所需要的,这是一个语法错误。

      scanf() 中,%d 需要int * 类型的参数,而不是int


      对于您的主要问题:

      "为什么我们不需要在 malloc 数组的情况下取消引用指针值?"

      它与malloc()ed 或动态内存分配无关。它与 C 中允许的指针算术语法有关。

      【讨论】:

        【解决方案4】:

        我认为一个小的可运行代码比我解释的更能帮助你理解。

        #include <stdlib.h>
        #include <stdio.h>
        
        int main() {
            int *p = (int *)malloc(sizeof(int)*5);
            int *q = (int *)malloc(sizeof(int)*5);
            int i = 0;
            int test_val = 100;
        
            for(i = 0; i < 5; i++) {
                *(p + i) = test_val;
                q[i] = test_val;
                test_val = test_val + 10;
            }
        
            for(i = 0; i < 5; i++)
                printf("p[%d] = %d\n", i, p[i]);
        
            printf("p[i] gives the value in array p at index i \n\n");
        
            for(i = 0; i < 5; i++)
                printf("q[%d] = %d\n", i, q[i]);
            
            printf("Note that the values in p and q are equal which means that *(x + 1) and x[i] are 2 ways to access the same values \n\n");
        
            for(i = 0; i < 5; i++)
                printf("*p + %d = %d\n", i, *p + i);
        
            printf("*p + i gets value pointed by p and adds i to it in each iteration \n\n");
        
            for(i = 0; i < 5; i++)
                printf("*(p + %d) = %d\n", i, *(p + i));
        
            printf("*(p + i) gets value pointed by p + i \n\n");
        
            int *j = (int *)malloc(sizeof(int)*5);
            int *k = (int *)malloc(sizeof(int)*5);
            int *l = (int *)malloc(sizeof(int)*5);
        
            for(i = 0; i < 5; i++) {
                 j[i] = test_val;
                 k[i] = test_val;
                 l[i] = test_val;
            }
            printf("(*j)++ = %d\n\n", (*j)++);
            printf("*(k++) = %d\n\n", *(k++));
            printf("*l++ = %d\n\n", *l++);
            
            for(i = 0; i < 5; i++) {
                 j[i] = test_val;
                 k[i] = test_val;
                 l[i] = test_val;
            }
            printf("test_val = %d\n\n", test_val);
            printf("++(*j) = %d\n\n", ++(*j));
            printf("*(++k) = %d\n\n", *(++k));
            printf("*++l = %d\n\n", *++l);
            return 0;
        }
        

        以下是输出:

        p[0] = 100
        p[1] = 110
        p[2] = 120
        p[3] = 130
        p[4] = 140
        p[i] gives the value in array p at index i 
        
        q[0] = 100
        q[1] = 110
        q[2] = 120
        q[3] = 130
        q[4] = 140
        Note that the values in p and q are equal which means that *(x + 1) and x[i] are 2 ways to access the same values 
        
        *p + 0 = 100
        *p + 1 = 101
        *p + 2 = 102
        *p + 3 = 103
        *p + 4 = 104
        *p + i gets value pointed by p and adds i to it in each iteration 
        
        *(p + 0) = 100
        *(p + 1) = 110
        *(p + 2) = 120
        *(p + 3) = 130
        *(p + 4) = 140
        *(p + i) gets value pointed by p + i 
        
        test_val = 150
        
        (*j)++ = 150
        
        *(k++) = 150
        
        *l++ = 150
        
        test_val = 150
        
        ++(*j) = 151
        
        *(++k) = 150
        
        *++l = 150
        

        对于scanf问题,scanf总是需要一个指针作为参数。 p[I] 是 (p + i) 处的值,因此不正确。理想情况下,您应该使用 scanf("%d", &(p[i]))

        【讨论】:

          【解决方案5】:

          说*p是一个变量

          没有。变量为p,其类型为int*


          • *p:在地址p 找到的值
          • *p+i 解析为(*p) + 1:地址p 的值然后将值1 添加到该值
          • p[i]*(p + i) 等效:地址 p + i 处的值,其中 p + i 是从 p 开始的整数数组的第 ith 元素的地址。又名从 p 开始的整数向量中的 ith 元素
          • *p++解析为*(p++):增加指针p(即使p指向下一个元素)并从p的旧值中获取值。

          【讨论】:

          • Re "变量为p,其类型为int*",这就是int* p的世界观。 int *p 的世界观是 *pint 类型的对象。
          • @ikegami 我不同意。在声明int* p 中,声明的变量是p,声明的变量类型是int *。在声明 *p 之外是一个表达式,它由应用于 id p 的尊重一元运算符组成,是的,它计算为 int 类型的对象。但是一个变量由一个符号(标识符)表示,该标识符是p,即变量的名称。
          • Re "我不同意",但你几乎一字不差地重复了我所说的话。我100%同意你说的,和我之前说的并不矛盾。
          • @ikegami 那么我误解了你,我很抱歉。
          • @ikegami: *p 是一个int 类型的表达式,它恰好是一个左值,但它不是一个object输入int。这样想只会造成混乱。将 p 替换为返回指针的函数调用 - *f() 仍然可以是左值,但您会认真考虑它是 object 吗?
          猜你喜欢
          • 1970-01-01
          • 2013-04-01
          • 2019-10-14
          • 1970-01-01
          • 2015-10-15
          • 1970-01-01
          • 2014-03-30
          • 2019-12-04
          • 1970-01-01
          相关资源
          最近更新 更多