繁体   English   中英

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

[英]C - how/why does this piece of code “work”?

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

我没有在我的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;
}

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

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

您的test函数需要整数地址和整数计数。 main函数正好给出了那些东西, &arrayt 所以它编译得很好。

不过, 即时你尝试去参考v[1]甚至计算出的地址v[N]其中N既不是零也不是一个,全盘皆输。 你已经给出了一个恰好是一个元素的数组,并且它是未定义的行为来执行上述任何一个

所以,当你的代码看起来工作(一),那将是对另一种实现方式,在另一台机器完全由事故,并且不保证,或在月亮的不同阶段,甚至。

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

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)不是真的,我似乎记得有一个实际的法律问题,声称申请专利的情况不真实,所以我需要澄清这只是幽默价值。

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

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

int array;

在堆栈上,你放置了4个字节,[1]并称之为array 然后,您传递该地址以进行测试:

test(&array, ...);

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

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

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

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

什么地址是&v[i] 让我们首先分解这种语法。 字面意思是读

  1. deference v( v[] )获取感兴趣的内存位置,
  2. 得到第i个项目(意思是看看远离* v的i * sizeof( *v )的内存位置,
  3. 最后将该& )的地址提供给scanf。

Terse语法,嗯? 所以,当i

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

在这一点上,你开始看到你根本没有创建一个阵列,而是从你的程序的内脏中制造出了牛肉。 百胜!

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

int main(void) {
    int array;
    int t = 10;

    // use %p inplace of %lu if the warnings are an issue; I'm not
    // fluent in hex, which is what both of my C compilers spit out
    printf("Value of 'array':   %d\n", array);
    printf("Address of 'array': %lu\n", &array);
    printf("Address of 't':     %lu\n", &t);
    test(&array, t);

    return 0;
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM