C语言:查找给定程序的输出

时间:2018-02-06 08:48:20

标签: c

所以我有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

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

编辑:

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

这跳1 integer?这会在街区内发生什么?

*p++++*p

之间有什么不同

4 个答案:

答案 0 :(得分:4)

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

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

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

C11 6.5.15 / 3:

  

对操作数的评估是不合理的。

*p++未按++*p排序。子表达式的评估顺序未指定,并且由于同一变量存在多个未测序的副作用,因此行为未定义。

同样,*p++*p无关。这也会导致不确定的行为。

总结:代码被破坏并且充满了错误。任何事情都可能发生。无论谁给你你的任务都是无能的。

答案 1 :(得分:3)

  

一开始我想了解第一个声明做什么

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

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

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

即可轻松实现

请注意,如果指针没有指向同一个数组或者指向后者的末尾,那么比较除(in-)相等之外的指针是未定义的行为(尽管如此,它们也是如此)。

通常,如果您愿意,可以将此比较设为p < a + sizeof(a)/sizeof(*a)(或sizeof(a)/sizeof(a[0])

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

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

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

然而,这是未定义的行为,因为p的读写访问之间没有序列点;您不知道是否首先评估作业的左侧或右侧,在前一种情况下,我们将分配a[i] = ++a[i + 1],在后一种情况下,a[i] = ++a[i]!您可能已经与另一个编译器获得了不同的输出!!!

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

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

答案 2 :(得分:0)

真正理解这种东西(内存管理,指针行为等)的唯一方法就是试验自己。无论如何,我闻到有人试图看起来聪明愚弄学生,所以我会尽量澄清一些事情。

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

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

A pointer <code>p</code> pointing to the first element in <code>a</code>.

这创建了一个五int的向量,到目前为止,非常好。鉴于数据,明显的举措是在使用p的元素上运行。您将执行以下操作:

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);
}

而不是使用指向a的指针指向int,代码会将p转换为指向char的指针。说你的机器是32位的。那么int可能会占用四个字节。当pint的指针时,当你执行++p时,你会有效地转到a中的下一个元素,因为透明地你的编译器将跳转四个字节。如果您将int指针转换为char,则无法添加NUM并假设您已成为数组的末尾:char存储在一个byte,所以((char *)p) + 5将指向a的第二个元素中的第二个字节,前提是它指向a的开头。这是你必须调用sizeof(int)并将其乘以NUM的方法,以获得数组的结尾。

最后,臭名昭着的*p++ = ++*p<60 ? *p : 0;。这对于面对学生来说是不公平的,因为正如其他人已经指出的那样,该代码的行为是不确定的。让我们通过表达来表达。

++*p表示&#34;访问p并在结果中添加1。如果p指向a的第一个位置,则结果为21. ++*p不仅会返回21,还会在您拥有20的位置的内存中存储21。如果你只回到21,你会写; *p + 1

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

*p++ = x表示&#34;将x的值存储在p指向的内存地址中,然后递增p。这就是为什么你在++p循环的增量部分找不到p++for的原因。

现在关于整个指令(*p++ = ++*p<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;
}

希望这有帮助。

答案 3 :(得分:0)

简短回答:因为这行

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

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

不幸的是,提出这个问题的人并不理解这一点,认为&#34;棘手&#34;代码行这可以解决问题,而不是不惜一切代价避免的事情。