您调用 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?),您分配 n 次 sizeof(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[] 编写)。 注意: main 是 type int 的函数,它返回一个值。请参阅:C11 Standard §5.1.2.2.1 Program startup p1 (draft n1570)。另请参阅:See What should main() return in C and C++?。 void main 在几乎所有系统和所有符合标准的系统中都是错误的。
查看一下,如果您还有其他问题,请告诉我。