【问题标题】:C language: find output of given programC语言:查找给定程序的输出
【发布时间】:2018-02-06 08:48:19
【问题描述】:

所以我有这个main:

#define NUM 5

int main()
{
    int a[NUM]={20,-90,450,-37,87};
    int *p;

    for (p=a; (char *)p < ((char *)a + sizeof(int) * NUM); )  //same meaning: for (p=a; p<a+NUM;)
        *p++ = ++*p < 60 ? *p : 0;  //same meaning: *(p++)=++(*p)<60?*p:0;

    for(p=a; (char *)p < ((char *)a + sizeof(int) * NUM); )
        printf("\n %d ", *p++);

    return 0;
}

我需要找到输出是什么。 所以在尝试理解之后我运行它,这是输出:

21
 -89
 0
 -36
 0

所以我很乐意解释如何解决这类问题(我很快就要考试了,这类问题我可能会看到..)

编辑:

一开始我想了解第一个forstatement 在做什么:

这个跳转 1 integer ?块内发生了什么?

*p++和++*p有什么区别

【问题讨论】:

  • 最好的办法是把你的调试器拿出来,逐行逐行执行。将此与构建您自己的小程序相结合。这是学习这些东西的唯一真正方法。
  • @Achilles:那是淘气的方式。请改用调试器。很好,上面加糖。
  • 我很确定*p++ = ++*p&lt;60 ? *p : 0; 是未定义的行为,因为您在多个地方访问和修改p 和*p。这意味着程序已损坏。请不要写这样的废话。
  • 我会咬紧牙关可能会失去分数,在问题上画一条线,然后在“垃圾代码,与良好的编码实践无关,应该从考试中删除不好的问题”之间写下。
  • @user2908206 这不是你的错,但这是一个可怕的问题。在你的考试中设置了一个可怕的问题,这对 SO 来说是一个可怕的问题。这个问题只测试和教导了一个教训,“永远不要编写编译器和任何人类读者都无法理解的棘手代码。”

标签: c


【解决方案1】:

这个问题与Why are these constructs (using ++) undefined behavior in C? 类似,但由于?: 运算符内的(微妙)序列点,它不是完全重复的。

由于程序包含未定义的行为,因此无法预测输出。

虽然由于?: 运算符的内部序列点,与*p 相比,子表达式++*p 以明确定义的方式排序,但对于子表达式的其他组合则不是这样。最值得注意的是,未指定对= 的操作数的评估顺序:

C11 6.5.15/3:

操作数的计算是无序的。

*p++ 没有相对于++*p 进行排序。子表达式的求值顺序未指定,由于同一变量有多个未排序的副作用,因此行为未定义。

同样,*p++ 没有相对于*p 进行排序。这也会导致未定义的行为。

总结:代码被破坏并且充满了错误。任何事情都有可能发生。给你任务的人是不称职的。

【讨论】:

    【解决方案2】:

    一开始我想了解第一个 for 语句在做什么

    这就是所谓的代码混淆......困难的部分显然是这个:

    (char *)p < ((char *)a+sizeof(int)*NUM);
    

    好的,我们将 p 转换为指向 char 的指针,然后将它与从数组 a 中检索到的另一个指针进行比较,该指针指向 a 之后的第一个元素:sizeof(int)*NUM 是数组的大小 - 我们可以得到更多只需拥有sizeof(a),所以(char*)p &lt; (char*)a + sizeof(a)

    请注意,如果指针不指向同一个数组或不指向后者末尾的一个数组(但在本例中是这样),则比较与(不)相等性以外的指针是未定义的行为。

    通常情况下,可以将此比较为p &lt; a + sizeof(a)/sizeof(*a)(或sizeof(a)/sizeof(a[0]),如果您愿意的话)。

    *p++ 增加指针并在之后取消引用,它是p = p + 1; *p = ... 的缩写。 ++*p,另一方面,首先取消引用指针并增加它指向的值(注意与 *++p 的区别,还有另一个变体 - 你能自己得到它吗?),i。 e.相当于*p = *p + 1。

    整行 *p++ = ++*p&lt;60 ? *p : 0; 然后应执行以下操作:

    • 增加*p的值
    • 如果结果小于60,使用它,否则使用0
    • 将此分配给*p
    • 递增p

    但是,这是未定义的行为,因为p 的读写访问之间没有序列点;你不知道赋值的左边还是右边首先被评估,在前一种情况下我们将分配a[i] = ++a[i + 1],在后一种情况下,a[i] = ++a[i]!你可能用另一个编译器得到了不同的输出!!!

    然而,这只是两个最有可能的输出——实际上,如果陷入未定义的行为,任何事情都可能发生,编译器可能只是忽略有问题的代码,决定不做什么都没有(只是作为第一条指令从 main right 退出),程序可能会崩溃,甚至可能会关闭太阳......

    请注意,具有未定义行为的单个位置会导致整个程序本身具有未定义行为!

    【讨论】:

      【解决方案3】:

      简答:因为这条线

      *p++ = ++*p<60 ? *p : 0;
      

      无法说出程序的行为方式。当我们访问右侧的*p时,是使用p的旧值还是新值,即左侧的p++之前还是之后? C 中没有规则可以告诉我们。相反,有一条规则表明,由于这个原因,代码是未定义。

      不幸的是,提出问题的人不理解这一点,认为“棘手”的代码行是一个令人困惑的事情,而不是不惜一切代价避免的事情。

      【讨论】:

        【解决方案4】:

        真正理解这类东西(内存管理、指针行为等)的唯一方法是自己进行实验。无论如何,我闻到有人试图装出聪明的样子来愚弄学生,所以我将尝试澄清一些事情。

        int a[NUM]={20,-90,450,-37,87};
        int *p;
        

        内存中的这个结构类似于:

        这会创建一个包含五个int 的向量,到目前为止,一切都很好。考虑到这些数据,显而易见的举动是使用 p 遍历 a 的元素。您将执行以下操作:

        for(p = a; p < (a + NUM); ++p) {
            printf("%d ", *p);
        }
        

        但是,首先要注意的变化是两个循环都将指针转换为char。所以,他们将是:

        for (p=a;(char *)p<((char *)a+sizeof(int)*NUM); ++p) {
            printf("%d ", *p);
        }
        

        代码将p转换为指向char的指针,而不是使用指向int的指针指向a。假设您的机器是 32 位的。那么int 可能会占用四个字节。由于p 是指向int 的指针,当您执行++p 时,您实际上会转到a 中的下一个元素,因为显然您的编译器将跳转四个字节。如果您将 int 指针转换为 char,则不能添加 NUM 并假设您不再是数组的末尾:char 存储在一个字节中,因此 ((char *)p) + 5 将指向到a 的第二个元素中的第二个字节,前提是它之前指向a 的开头。也就是说,您必须调用 sizeof(int) 并将其乘以 NUM,才能得到数组的末尾。

        最后是臭名昭著的*p++ = ++*p&lt;60 ? *p : 0;。这对学生来说是不公平的,因为正如其他人已经指出的那样,该代码的行为是未定义的。让我们逐个表达。

        ++*p的意思是“访问p,结果加1。如果p指向a的第一个位置,那么结果就是21。++*p不仅返回21,而且也将 21 存储在内存中你有 20 的地方。如果你只返回 21,你会写;*p + 1。

        ++*p&lt;60 ? *p : 0表示“如果p指向的值永久加1的结果小于60,则返回该结果,否则返回0。

        *p++ = x的意思是“将x的值存储在p指向的内存地址中,然后递增p。这就是为什么在递增部分找不到++p或p++的原因for 循环。

        现在关于整个指令 (*p++ = ++*p&lt;60 ? *p : 0;),它是未定义的行为(查看@Lundin 的答案以获取更多详细信息)。总之,最明显的问题是您不知道赋值运算符周围的哪个部分(左侧或右侧)将首先被评估。您甚至不知道赋值运算符右侧表达式中的子表达式将如何被求值(以何种顺序)。

        你怎么能解决这个问题?其实很简单:

        for (p=a;(char *)p<((char *)a+sizeof(int)*NUM); ++p) {
            *p = (*p + 1) <60 ? (*p + 1) : 0;
        }
        

        而且更具可读性。更好:

        for (p = a; p < (a + NUM); ++p) {
            *p = (*p + 1) <60 ? (*p + 1) : 0;
        }
        

        希望这会有所帮助。

        【讨论】:

        • 您无法通过实验“了解未定义行为的结果”,因为结果可能是任何行为。不保证该行为是确定性的或可重复的。这不像只是一个未指定的评估顺序问题那么容易被忽略,它比这更深入。也就是说,问题不是程序员不知道顺序,问题是编译器不知道顺序。
        • 我在回答中写了很多东西,除了我在最后提到的未定义行为。不管怎样,我已经澄清了第一句话。
        • “问题是,你不知道赋值运算符周围的哪个部分(左边或右边)将首先被评估,所以结果可能在不同的编译器中有所不同。”具体来说,这只是未定义行为的冰山一角。如果唯一的问题是未指定的评估顺序,那么至少代码将以某种未指定的方式表现出确定性。但它不会崩溃和燃烧。
        • @Lundin,如果您对地雷感到如此失望,我建议您写下自己的答案。还是谢谢你。
        • 我已经这样做了,但我认为指出未指定的评估顺序和普通未定义行为之间的区别很重要。前者要么根本不是问题,要么导致意外/不可预测的结果。后者可能会使整个程序崩溃。除此之外,你的答案很好。
        猜你喜欢
        • 1970-01-01
        • 2013-07-16
        • 1970-01-01
        • 2021-08-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-05
        • 1970-01-01
        相关资源
        最近更新 更多