C -这段代码是如何工作的?

时间:2022-09-02 12:14:52

I've written this code and "it works" — but how?

我写了这段代码,“它能工作”——但是怎么做呢?

I haven't declared an array in my main function; I simply added a standard integer into a procedure with a pointer. For example, it'll scan an array of 2 or 20 integers by changing how many times the loop will run.

我还没有在主函数中声明数组;我只是在一个带有指针的过程中添加了一个标准的整数。例如,它将通过改变循环运行的次数来扫描一个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;
}

I didn't really know exactly what I was writing, until I realized it was working. I've tried to search about "array of pointers", or pointers in general but couldn't find any specific answer to this example above. I wish I knew more what exactly to look for.

我真的不知道我在写什么,直到我意识到我在写。我尝试过搜索“指针数组”,或者一般的指针,但是没有找到上面这个例子的任何特定答案。我希望我知道更多确切要找的东西。

2 个解决方案

#1


11  

This code works, for only for some values of the word "work" :-)

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

Your test function wants the address of an integer and a integer count. The main function gives it exactly those things, &array and t. So it compiles just fine.

您的测试函数需要一个整数和一个整数计数的地址。主函数给出了这些东西&数组和t,所以编译很好。

However, the instant you try to de-reference v[1] or even calculate the address of v[N] where N is neither zero nor one, all bets are off. You have given an array of exactly one element and it's undefined behaviour to do either of those mentioned things

然而,当你试着去引用v[1]或者甚至计算v[N]的地址时,N既不是0也不是1,所有的赌注都没有了

So, while your code may seem to work(a), that will be entirely by accident and not guaranteed on another implementation, another machine, or even during a different phase of the moon.

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

You can, of course, fix this by ensuring you don't try to do anything beyond the end of the array. with something like:

当然,您可以通过确保不尝试在数组的末尾之外执行任何操作来修复这个问题。可以这样说:

int array;
test(&array, 1);

or:

或者:

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

This will ensure the count values passed through to the function matches the size of said array.

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


For the language lawyers amongst us, C11 Appendix J.2 lists these items as being undefined behaviour (the first covers calculating v[N] where N is neither zero nor one, the second covers de-referencing v[1]):

对于我们当中的语言律师来说,C11附录J.2列出了这些未定义的行为(第一个包含计算v[N],其中N既不是0也不是1,第二个包含去引用v[1]):

Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that does not point into, or just beyond, the same array object (6.5.6).

对数组对象和整数类型的指针的添加或减除产生的结果不会指向或超过相同的数组对象(6.5.6)。

Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that points just beyond the array object and is used as the operand of a unary * operator that is evaluated (6.5.6).

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

That referenced 6.5.6 /8 states:

其中提到6.5.6 /8说:

If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

如果指针操作数和结果点都指向同一数组对象的元素,或数组对象的最后一个元素之后的元素,则计算不产生溢出;否则,行为就没有定义。


(a) The possibility that undefined behaviour sometimes "works" is actually its most insidious feature. If it never worked, it would be so much better.

(a)没有定义的行为有时“起作用”的可能性实际上是其最阴险的特征。如果它从来没有起过作用,那就会好得多。

That's why I have a patent pending(b) on a device that connects electrodes to your private parts that get activated whenever the compiler detects use of said behaviour.

这就是为什么我有一个正在申请的专利(b)在一个设备上,连接电极到你的私有部分,当编译器检测到使用这些行为时就会被激活。

It has increased considerably the quality of the code our shop delivers. Now if only we could retain our staff, that would be excellent :-)

它大大提高了我们商店提供的代码的质量。如果我们能留住我们的员工,那就太好了。


(b) Not really, I seem to recall there's an actual legal issue with claiming patent pending status untruthfully so I need to clarify that this was for humour value only.

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

#2


1  

Your code works by "happy accident". If you pick a large enough number, for example, it should segfault. Why? Let's see...

您的代码是通过“愉快的意外”工作的。如果你选择一个足够大的数字,例如,它应该是segfault。为什么?让我们看看…

In main(), you declare an integer on the stack:

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

int array;

On the stack, you've placed 4 bytes,[1] and called it array. Then, you pass the address of that to test:

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

test(&array, ...);

For simplicity of explanation, since array is the first variable in your program's stack, we'll call it address 0. By the time you get to test()'s first code line (for ...), then, your stack looks something like:

为了便于解释,由于数组是程序堆栈中的第一个变量,我们将它命名为address 0。当您测试()的第一个代码行(for…)时,您的堆栈看起来如下:

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

Then, during the first iteration of the first for-loop, the code puts integers somewhere with:

然后,在第一个for循环的第一次迭代中,代码将整数放在如下位置:

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

What address is &v[i]? Let's break down that syntax first. That literally reads

增加了[我]是什么地址?让我们先分解一下语法。字面上阅读

  1. deference v (v[]) to get the memory location of interest,
  2. 为了得到感兴趣的内存位置,
  3. get the i'th item (meaning look at the memory location that is i * sizeof( *v ) away from *v),
  4. 获取第i项(意思是查看i * sizeof(*v)与*v之间的内存位置),
  5. and finally give the address of that (&) to scanf.
  6. 最后将那个(&)的地址给scanf。

Terse syntax, eh? So, when i is

简洁的语法,是吗?所以,当我是

  • 0, this would pass &v[0], or &(*(v + 0)), or address 0 to scanf.
  • 0,这将传递&v[0],或&(*(v + 0)),或地址0到scanf。
  • 1, this would pass &v[1], or &(*(v + 1)), or address 4 to scanf.
  • 1,这将通过&v[1]或&(*(v + 1))或地址4到scanf。
  • 2, this would pass &v[2], or &(*(v + 2)), or address 8 to scanf.
  • 2、这将通过&v[2]或&(*(v + 2))或地址8到scanf。
  • ...

At which point you begin to see that you haven't created an array at all, but rather are making ground-beef out of your program's innards. Yum!

此时,您将开始看到,您根本没有创建数组,而是从程序的内部构造出磨碎的内容。Yum !

Finally what really helped me grok this detail awhile back was seeing the memory addresses on my machine. You might consider printing out the memory addresses of all of your variables. For example, you might annotate main() with:

最后,真正让我回想起这个细节的是看到我的机器上的内存地址。您可以考虑打印所有变量的内存地址。例如,您可以用以下方式注释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] The pedantically minded will correctly complain that 4 bytes is arbitrary and certainly not guaranteed across all architectures. This post is in response to a self-described noob, so there are some intentional pedagogical tradeoffs.

[1]有思想的人会正确地抱怨4个字节是任意的,当然在所有的体系结构中都没有保证。这篇文章是对一个自我描述的noob的回应,因此存在一些有意的教学权衡。

#1


11  

This code works, for only for some values of the word "work" :-)

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

Your test function wants the address of an integer and a integer count. The main function gives it exactly those things, &array and t. So it compiles just fine.

您的测试函数需要一个整数和一个整数计数的地址。主函数给出了这些东西&数组和t,所以编译很好。

However, the instant you try to de-reference v[1] or even calculate the address of v[N] where N is neither zero nor one, all bets are off. You have given an array of exactly one element and it's undefined behaviour to do either of those mentioned things

然而,当你试着去引用v[1]或者甚至计算v[N]的地址时,N既不是0也不是1,所有的赌注都没有了

So, while your code may seem to work(a), that will be entirely by accident and not guaranteed on another implementation, another machine, or even during a different phase of the moon.

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

You can, of course, fix this by ensuring you don't try to do anything beyond the end of the array. with something like:

当然,您可以通过确保不尝试在数组的末尾之外执行任何操作来修复这个问题。可以这样说:

int array;
test(&array, 1);

or:

或者:

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

This will ensure the count values passed through to the function matches the size of said array.

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


For the language lawyers amongst us, C11 Appendix J.2 lists these items as being undefined behaviour (the first covers calculating v[N] where N is neither zero nor one, the second covers de-referencing v[1]):

对于我们当中的语言律师来说,C11附录J.2列出了这些未定义的行为(第一个包含计算v[N],其中N既不是0也不是1,第二个包含去引用v[1]):

Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that does not point into, or just beyond, the same array object (6.5.6).

对数组对象和整数类型的指针的添加或减除产生的结果不会指向或超过相同的数组对象(6.5.6)。

Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that points just beyond the array object and is used as the operand of a unary * operator that is evaluated (6.5.6).

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

That referenced 6.5.6 /8 states:

其中提到6.5.6 /8说:

If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

如果指针操作数和结果点都指向同一数组对象的元素,或数组对象的最后一个元素之后的元素,则计算不产生溢出;否则,行为就没有定义。


(a) The possibility that undefined behaviour sometimes "works" is actually its most insidious feature. If it never worked, it would be so much better.

(a)没有定义的行为有时“起作用”的可能性实际上是其最阴险的特征。如果它从来没有起过作用,那就会好得多。

That's why I have a patent pending(b) on a device that connects electrodes to your private parts that get activated whenever the compiler detects use of said behaviour.

这就是为什么我有一个正在申请的专利(b)在一个设备上,连接电极到你的私有部分,当编译器检测到使用这些行为时就会被激活。

It has increased considerably the quality of the code our shop delivers. Now if only we could retain our staff, that would be excellent :-)

它大大提高了我们商店提供的代码的质量。如果我们能留住我们的员工,那就太好了。


(b) Not really, I seem to recall there's an actual legal issue with claiming patent pending status untruthfully so I need to clarify that this was for humour value only.

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

#2


1  

Your code works by "happy accident". If you pick a large enough number, for example, it should segfault. Why? Let's see...

您的代码是通过“愉快的意外”工作的。如果你选择一个足够大的数字,例如,它应该是segfault。为什么?让我们看看…

In main(), you declare an integer on the stack:

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

int array;

On the stack, you've placed 4 bytes,[1] and called it array. Then, you pass the address of that to test:

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

test(&array, ...);

For simplicity of explanation, since array is the first variable in your program's stack, we'll call it address 0. By the time you get to test()'s first code line (for ...), then, your stack looks something like:

为了便于解释,由于数组是程序堆栈中的第一个变量,我们将它命名为address 0。当您测试()的第一个代码行(for…)时,您的堆栈看起来如下:

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

Then, during the first iteration of the first for-loop, the code puts integers somewhere with:

然后,在第一个for循环的第一次迭代中,代码将整数放在如下位置:

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

What address is &v[i]? Let's break down that syntax first. That literally reads

增加了[我]是什么地址?让我们先分解一下语法。字面上阅读

  1. deference v (v[]) to get the memory location of interest,
  2. 为了得到感兴趣的内存位置,
  3. get the i'th item (meaning look at the memory location that is i * sizeof( *v ) away from *v),
  4. 获取第i项(意思是查看i * sizeof(*v)与*v之间的内存位置),
  5. and finally give the address of that (&) to scanf.
  6. 最后将那个(&)的地址给scanf。

Terse syntax, eh? So, when i is

简洁的语法,是吗?所以,当我是

  • 0, this would pass &v[0], or &(*(v + 0)), or address 0 to scanf.
  • 0,这将传递&v[0],或&(*(v + 0)),或地址0到scanf。
  • 1, this would pass &v[1], or &(*(v + 1)), or address 4 to scanf.
  • 1,这将通过&v[1]或&(*(v + 1))或地址4到scanf。
  • 2, this would pass &v[2], or &(*(v + 2)), or address 8 to scanf.
  • 2、这将通过&v[2]或&(*(v + 2))或地址8到scanf。
  • ...

At which point you begin to see that you haven't created an array at all, but rather are making ground-beef out of your program's innards. Yum!

此时,您将开始看到,您根本没有创建数组,而是从程序的内部构造出磨碎的内容。Yum !

Finally what really helped me grok this detail awhile back was seeing the memory addresses on my machine. You might consider printing out the memory addresses of all of your variables. For example, you might annotate main() with:

最后,真正让我回想起这个细节的是看到我的机器上的内存地址。您可以考虑打印所有变量的内存地址。例如,您可以用以下方式注释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] The pedantically minded will correctly complain that 4 bytes is arbitrary and certainly not guaranteed across all architectures. This post is in response to a self-described noob, so there are some intentional pedagogical tradeoffs.

[1]有思想的人会正确地抱怨4个字节是任意的,当然在所有的体系结构中都没有保证。这篇文章是对一个自我描述的noob的回应,因此存在一些有意的教学权衡。