C - 这段代码如何/为什么“起作用”?

时间:2018-04-13 03:35:04

标签: c arrays pointers

我写过这段代码并且“它有用” - 但是怎么样?

我没有在main函数中声明一个数组;我只是在带有指针的过程中添加了一个标准整数。例如,它将通过改变循环运行的次数来扫描2或20个整数的数组。

#include <stdio.h>

void test(int *v, int n) {
    int i;
    for (i = 0; i < n; i++) {
        printf("[%d]: ", i);
        scanf("%d", &v[i]); 
    }
    printf("\n\n#############\n\n");
    for (i = 0; i < n; i++) {
        printf("[%d]: %d\n", i, v[i]);
    }
}

int main(void) {
    int array;
    int t = 10; 
    test(&array, t);
    return 0;
}

我真的不知道我写的是什么,直到我意识到它正在发挥作用。我试图搜索“指针数组”或一般指针,但找不到上面这个例子的任何具体答案。我希望我知道更多的内容。

2 个答案:

答案 0 :(得分:11)

此代码适用于“work”一词的某些值: - )

您的test函数需要整数地址和整数计数。 main函数准确地为它们提供了&arrayt。所以它编译就好了。

但是,即时您尝试取消引用v[1]甚至计算 v[N]的地址N所有的赌注都不是零,也不是一个。你已经给出了一个恰好是一个元素的数组,并且它是未定义的行为来做任何提到的事情

因此,虽然您的代码可能似乎工作(a),但这完全是偶然的,并不能保证在另一个实现,另一台机器上,甚至在月亮的不同阶段。

当然,您可以通过确保尝试在数组末尾之外执行任何操作来解决此问题。有类似的东西:

int array;
test(&array, 1);

或:

int array[42];
test(array, sizeof(array) / sizeof(*array));

这将确保传递给函数的计数值与所述数组的大小相匹配。

对于我们中的语言律师,C11 Appendix J.2将这些项目列为未定义的行为(第一项涵盖计算v[N],其中N既不是零也不是一,第二项涵盖取消引用v[1]):

  

将指针加到或减去数组对象和整数类型会产生一个不指向或超出同一数组对象的结果(6.5.6)。

     

将指针加到或减去数组对象和整数类型会产生一个指向数组对象之外的结果,并用作被计算的一元*运算符的操作数(6.5.6) )。

引用了6.5.6 /8个州:

  

如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出;否则,行为未定义。

(a)未定义行为有时“有效”的可能性实际上是其最隐蔽的特征。如果从不工作,那就更好了。

这就是为什么我在连接电极到您的私有部件的设备上有一个正在申请专利(b)的原因,只要编译器检测到使用了这种行为,它就会被激活。

它大大提高了我们商店提供的代码质量。现在,只要我们能够留住我们的员工,那就太棒了: - )

(b)不是真的,我似乎记得有一个实际的法律问题,即不真实地申请专利申请状态,所以我需要澄清这只是为了幽默价值。

答案 1 :(得分:1)

你的代码是“快乐意外”。例如,如果你选择一个足够大的数字,它应该是段错误的。为什么?我们看看......

main()中,您在堆栈上声明一个整数:

int array;

在堆栈上,您放置了4个字节,[1]并将其称为array。然后,您将地址传递给test:

test(&array, ...);

为了简化说明,由于array是程序堆栈中的第一个变量,我们将其称为地址0.当您到达test()的第一个代码行时{{1} 1}}),然后,你的堆栈看起来像:

for ...

然后,在第一个for循环的第一次迭代中,代码将整数放在某处

Address   | Variable name   | Value
 16       |  i              |  ???
 12       |  n              |  10
  8       |  v              |  --> 0  (Pointer to 0)
  4       |  t              |  10
  0       |  array          |  ???

scanf("%d", &v[i]); 的地址是什么?让我们首先分解这种语法。字面意思是

  1. deference v(&v[i])获取感兴趣的记忆位置,
  2. 获取第i个项目(意味着查看远离* v的v[]的内存位置),
  3. 最后将的地址i * sizeof( *v ))提供给scanf。
  4. Terse语法,是吗?那么,当&

    • i,会将0&v[0]或地址0传递给&(*(v + 0))
    • scanf,这会将1&v[1]或地址4传递给&(*(v + 1))
    • scanf,这会将2&v[2]或地址8传递给&(*(v + 2))
    • ...

    此时你开始看到你根本没有创建一个阵列,而是从你的程序的内脏中制作出了牛肉。百胜

    最后真正帮助我了解这个细节的一段时间是看到我机器上的内存地址。您可以考虑打印出所有变量的内存地址。例如,您可以使用:

    注释scanf
    main()

    [1]迂腐的人会正确地抱怨4个字节是任意的,并且肯定不能保证所有架构。这篇文章是对一个自我描述的菜鸟的回应,所以有一些有意识的教学权衡。