关于C语言指针的相同输出

时间:2018-03-24 02:42:59

标签: c arrays pointers

以下是一个简单的代码:

#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*/并将其他两个打印行作为注释行。

我的问题是:为什么三条不同的印刷线具有相同的输出?我的理解是打印2和3具有相同的含义,但是它们的地址...打印1有一个解除引号(*)所以它是双重类型的数字。我很困惑,有人可以给出明确的解释吗?

附上编译信息:

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

但奇怪的是:如果我直接执行代码而不是先调试它,那就没有错误,所有这些都会输出正确的结果(10 0.000000)。

3 个答案:

答案 0 :(得分:0)

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

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;
}

答案 1 :(得分:0)

您通过尝试使用调用double*在每次迭代中将double打印为printf("%f\n",a++);来调用未定义行为,然后尝试访问超出从printf (" %f\n", a[i]);开始i = 3以及for循环的每次后续迭代调用double *a;时,已分配的内存块结束。

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

当您声明a时,double指向双重的指针,而不是printf("%f\n",a++);本身。什么是指针?指针只是一个变量,它将地址保存为其值。当您尝试调用'*'时,您试图打印指针,而不是指针所占地址的值(您通过取消引用指针与一元{{1}获得的指针) }运算符,例如*a)。

这是C11 §7.21.6.1(p2 & p9) The fprintf function - insufficient arguments, or incorrect type.

中描述的未定义行为

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

当您使用a注意:参见:Do I cast the result of malloc?)为a = calloc (n, sizeof(double));分配内存时,您将n次分配sizeof(double)使用calloc将字节初始化为全零。 (大多数现代桌面上的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++);的下一次调用会调用 Undefined Behavior ,然后再次使用post增量运算符使a指向第三个元素,例如< / p>

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

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

*(a + i)循环中的每次迭代中,通过在循环中使用fora指向16个字节(或两个double值)的位置。在第4次迭代(a++)之前,在您调用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]),您尝试从8字节(或一个*(a + 3)的地址打印一个值。已分配块的结尾。在下一次迭代结束时,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); {1}}仍然指向已分配的内存块的开头。在您的情况下,尝试a可能会导致立即崩溃。

当您调用未定义的行为时,代码的行为可以执行从正确显示到SegFault的操作。 (它将做的是 undefined )。它是不正确的代码。所以不要违背你对编译器的承诺,不要试图在分配的内存块之外读取或写入地址。

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

仔细看看,如果您有其他问题,请告诉我。

答案 2 :(得分:0)

正如其他人所说,在使用%f的{​​{1}}转换说明符时传递指针的行为没有C标准定义。

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