【问题标题】:Same output about Pointers in C language关于 C 语言中指针的相同输出
【发布时间】:2018-09-02 18:45:42
【问题描述】:

下面是一个简单的代码:

#include <stdio.h>
#include <stdlib.h>

void main(void)
{
   int i,n=10;
   double *a;
   a = (double *)calloc(n,sizeof(double));
   if (!a){
       printf("Allocating error!\n");
       exit(1);
   }
   for (i=0;i<10;i++){
       printf("%f\n",*a++); /*Print 1*/
       printf("%f\n",a++);  /*Print 2*/
       printf("%f\n",a[i]); /*Print 3*/
   }
}

注意:三个打印行是分开测试的,比如运行printf("%f\n",*a++); /*Print 1*/,让另外两个打印行作为注释行。

我的问题是:为什么三个不同的打印行有相同的输出?我的理解是 Print 2 和 3 具有相同的含义,但它们是地址...... Print 1 有一个取消引用符号 (*) 所以它是双精度类型的数字。我很困惑,谁能给个清楚的解释?

附加编译信息:

warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘double *’ [-Wformat=]
        printf("%f\n",a++);  /*Print 2*/

但奇怪的是:如果我直接执行代码而不是先调试它,没有错误,并且都输出正确的结果(十个0.000000)。

【问题讨论】:

    标签: c arrays pointers


    【解决方案1】:

    首先编译你的程序,使用如下警告启用标志,它可能会告诉你一些事情。

    gcc -g -O -Wall test.c
    

    这里是解释。

    int  main()
    {
            int i,n=10;
            double *a;
            a = (double *)calloc(n,sizeof(*a));/* use sizeof(*a) instead of sizeof(double) */
            if (!a){
                    printf("Allocating error!\n");
                    exit(1);
            }
            for (i=0;i<10;i++)
                    printf("%lf\n",*a++); /* use %lf as a is of double ptr type
                                            it prints 0.00 everytimes not 1 as calloc zeroed 
                                            whole memory */
    
            /* when loop fails where a points ? */
            printf("%p\n",a++);  /* it prints address and use %p instead of %f */
            printf("%lf\n",a[i]); /* it results in undefined behaviour 
                                    as you are trying to access out of boundry like a[11] 
                                    which you didn't allocated */
    
            return 0;
    }
    

    【讨论】:

    • %f 适用于在printf 中格式化double 参数。它被定义为采用double 值。
    • 但是如果使用 %f*a 将只打印第一个 4 byte(GCC) 中的数据,如果我错了,请纠正我。
    • 不好意思,忘了说,三个打印行是分开测试的,比如运行printf("%f\n",*a++); /*Print 1*/,让另外两个打印行作为注释行。
    • @achal:你错了。在printf("%f\n",*a++) 中,a 指向一个双精度数。指针递增,并检索最初指向的doubledouble 被传递给 printff 转换说明符 fot printf 在 C 标准中定义 以采用 double 参数。它这样做并转换double 值。 (scanf 被定义为将一个指向 float 的指针用于%f 和一个指向double 的指针用于%lf。对于printf,它们都是double。)
    • 感谢@EricPostpischil,我认为printfscanf 的工作原理相同。这是非常有用的。我纠正了自己。
    【解决方案2】:

    您调用 Undefined Behavior 通过尝试在每次迭代中使用您的调用 printf("%f\n",a++);double 打印为 double 然后再次尝试访问超出分配块末尾的值来调用从i = 3 开始以及您的for 循环的每次后续迭代,您对printf (" %f\n", a[i]); 的调用的内存。

    为什么会发生奇怪的事情?

    当您声明 double *a; 时,a 是一个 指向 double 的指针,而不是 double 本身。什么是指针?指针只是一个变量,它保存着其他东西的地址作为它的值。当您尝试调用 printf("%f\n",a++); 时,您是在尝试打印指针,而不是指针持有的地址处的值(您通过 dereferencing 使用一元 '*' 运算符获得指针,例如*a)。

    这是C11 §7.21.6.1(p2 & p9) The fprintf function - insufficient arguments, or incorrect type. 中描述的未定义行为

    接下来,您尝试使用for 循环遍历每个元素,同时在循环内递增指针,这会导致您尝试读取超出以i = 3 开头的已分配块末尾的值。

    当您使用 a = calloc (n, sizeof(double));a 分配内存时(注意: 请参阅:Do I cast the result of malloc?),您分配 nsizeof(double) 使用 @987654343 初始化为全零的字节@。 (在大多数现代桌面上为 80 字节)。您为 10 doubles 分配了可以通过索引访问的空间:

    +---+---+---+---+---+-  /  -+---+---+
    | 0 | 1 | 2 | 3 | 4 | ..\ ..| 8 | 9 |
    +---+---+---+---+---+-  /  -+---+---+
    

    您在分配内存时向编译器做出的承诺之一是您只会尝试在块中写入或访问值,(您可以引用最后一个值之后的字节,但不能尝试读取或写在你分配的块之外的那个地址或任何其他地址。

    当您开始循环时,a 将地址保存到已分配内存块的开头,例如

    +---+---+---+---+---+-  /  -+---+---+
    | 0 | 1 | 2 | 3 | 4 | ..\ ..| 8 | 9 |
    +---+---+---+---+---+-  /  -+---+---+
    ^
    a
    

    在您第一次调用printf("%f\n",*a++); 时,后增量运算符(例如a++)的副作用 是指针a 提前sizeof *a 字节(或一个double)。这就是指针算法的全部基础。因此,在您调用printf("%f\n",*a++); 之后,a 指向第二个元素,例如

    +---+---+---+---+---+-  /  -+---+---+
    | 0 | 1 | 2 | 3 | 4 | ..\ ..| 8 | 9 |
    +---+---+---+---+---+-  /  -+---+---+
        ^
        a
    

    您对printf("%f\n",a++); 的下一次调用会调用未定义行为,然后使用后自增运算符再次推进指针,使a 指向第三个元素,例如

    +---+---+---+---+---+-  /  -+---+---+
    | 0 | 1 | 2 | 3 | 4 | ..\ ..| 8 | 9 |
    +---+---+---+---+---+-  /  -+---+---+
            ^
            a
    

    然后你调用printf("%f\n",a[i]); 打印第三个元素,你for 循环递增i = 1,然后循环重复,除了每个递增,当你调用printf("%f\n",a[i]); 时,你引用 a 的当前值加上索引。含义 a[i] 只是数组索引表示法,相当于指针表示法中的 *(a + i)

    for 循环中的每次迭代,通过在循环中使用 a++,您将在 a 指向的位置前进 16 个字节(或两个 double 值)。在您的第 4 次迭代中(i = 3),在您调用 printf("%f\n",a[i]); 之前,a 指向您分配块的最后一个元素的前一个,例如

    +---+---+---+---+---+-  /  -+---+---+---+---+---+---+---+---+
    | 0 | 1 | 2 | 3 | 4 | ..\ ..| 8 | 9 |  you don't own this   |
    +---+---+---+---+---+-  /  -+---+---+---+---+---+---+---+---+
                                ^           ^
                                a         a[3]
    

    因此,当您尝试使用a[3](例如*(a + 3))进行打印时,您是在尝试从8 字节地址(或一个double)中打印一个值在分配块的末尾之后。在下一次迭代结束时,a 不再指向您分配的内存块中的任何位置,例如

    +---+---+---+---+---+-  /  -+---+---+---+---+---+---+---+---+
    | 0 | 1 | 2 | 3 | 4 | ..\ ..| 8 | 9 |  you don't own this   |
    +---+---+---+---+---+-  /  -+---+---+---+---+---+---+---+---+
                                        ^
                                        a
    

    此外,您失去了free(a) 的能力,因为您未能保留指向已分配内存块开头的指针。如果您分配a = calloc (n, sizeof(double));,在没有先保存起始地址的副本之前,永远不要增加a 本身。一般你不会迭代a,而是第二个指针,比如double *p = a;,那么你可以随意p++,仍然可以free(a);,因为a仍然指向开头分配的内存块。在您的情况下,尝试free(a) 可能会导致立即崩溃。

    当您调用未定义行为时,您的代码行为可以做任何事情,从看起来正确到 SegFault'ing。 (它会做的是undefined)。这是格式错误的代码。所以不要违背你对编译器的承诺,不要试图读取或写入分配的内存块之外的地址。

    最后,main 的正确声明是 int main (void)int main (int argc, char **argv)(您将看到用等效的 char *argv[] 编写)。 注意: maintype int 的函数,它返回一个值。请参阅:C11 Standard §5.1.2.2.1 Program startup p1 (draft n1570)。另请参阅:See What should main() return in C and C++?void main 在几乎所有系统和所有符合标准的系统中都是错误的。

    查看一下,如果您还有其他问题,请告诉我。

    【讨论】:

    • 如问题所述,执行的代码一次只包含一个显示的printf 语句。分配的存储没有溢出。唯一的错误是传递%f 的指针。在您回答前两个多小时,此声明已被编辑成问题。
    【解决方案3】:

    正如其他人所指出的,在为printf 使用%f 转换说明符时传递指针具有C 标准未定义的行为。

    它在您的测试中打印为零的原因可能是因为应该传递 double 参数的寄存器包含零。许多处理器具有用于整数值和浮点值的单独寄存器。当printf 打印double 值时,它从指定用于传递浮点参数的寄存器中获取值。由于您传递了一个指针,因此它的值被放入指定用于传递整数类型参数的寄存器中,并且浮点寄存器没有更改。由于您的程序不包含任何实际的浮点运算,因此该寄存器可能根本没有在程序中使用,因此它仍然包含操作系统在启动进程时将其初始化为的零。那个零被打印出来了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多