-5

So i have this 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;
}

And i need to find what is the output. So after try to understand without any idea i run it and this is the output:

21
 -89
 0
 -36
 0

So i will glad to explanation how to solve this kind of questions (i have exam soon and this type of questions probably i will see..)

EDIT:

at the beginning i want to understand what the first forstatement doing:

This jump 1 integer ? and what this going inside the block ?

And what is the different between *p++ and ++*p

Murphy
  • 3,827
  • 4
  • 21
  • 35
user2908206
  • 265
  • 3
  • 16
  • 5
    The best way is to get your debugger out and step through line by line. Couple this with building small programs of your own. It's the only real way to learn this stuff. – Bathsheba Feb 06 '18 at 08:49
  • @Achilles: That's the naughty way. Use a debugger instead. Pretty please, with sugar on top. – Bathsheba Feb 06 '18 at 08:56
  • 5
    I am pretty sure `*p++ = ++*p<60 ? *p : 0;` is *undefined behaviour*, since you are accessing and modifying `p` and `*p` in multiple places. It means that program is broken. Please don't write nonsense like this. – user694733 Feb 06 '18 at 08:57
  • @Bathsheba Sure, but if someone's asking help about the output of a program, I would assume it would be a challenge getting their head around a debugger like GDB. Print statements are fine (IMO)to get in the habit of debugging :) – Faheem Feb 06 '18 at 09:01
  • On the UB point, see https://stackoverflow.com/questions/10995445/ternary-operator-and-sequence-points-in-c – Bathsheba Feb 06 '18 at 09:02
  • @Achilles: I sort of see your point but I'm quixotic on this point: learning to use a debugger is more important than learning to code. Using print statements breeds indolence. I started out using vi and the Watcom debugger. – Bathsheba Feb 06 '18 at 09:13
  • I do not have any debugger in the exam, i want first to understand this first for block and different between *p++ and ++*p – user2908206 Feb 06 '18 at 09:16
  • @user2908206 -- the point of using the debugger is not to have a tool to take exams with; use the debugger to understand how code behaves. If you need extra motivation, you can use this knowledge to ace your exams. – ad absurdum Feb 06 '18 at 09:21
  • @Bathsheba UB point: Does the mere existence of a sequence point in the right hand of an equation enforce the right hand being evaluated before the left hand, too, or is the sequence point just to be considered within the right hand internally only (so that still left and right hand can be evaluated in arbitrary order)? As far as I understand, it is the latter, in which case we *have* UB... – Aconcagua Feb 06 '18 at 09:33
  • @Aconcagua: You are correct, it is the latter. – Bathsheba Feb 06 '18 at 09:35
  • 2
    I would just bite the bullet of maybe losing the marks, draw a coule lines through the question and write between 'garbage code, irrelevant to good coding practice, bad questions should be removed from exams'. – Martin James Feb 06 '18 at 09:55
  • It won't even compile with those non-C `“` and `“` characters in there. – Toby Speight Feb 06 '18 at 09:58
  • Well I wrote an answer but I wonder if we shouldn't just have closed this as a dupe to the #1 SO C programming FAQ. The detail about the ?: operator is perhaps not significant enough to preserve this post. – Lundin Feb 06 '18 at 10:02
  • 4
    @user2908206 It's not your fault, but this is a terrible, terrible question. It was a terrible question to have been set on your exam, and it's a terrible question for SO. The question tests and teaches nothing other than the lesson, "Never write tricksy code that neither the compiler nor any human reader can understand." – Steve Summit Feb 06 '18 at 10:05
  • As to your last question, `*p++` post-increments `p`; `++*p` pre-increments `*p`. The former is `*(p++)`, not `(*p)++`. – molbdnilo Feb 06 '18 at 10:11
  • @SteveSummit I could not agree more. Profs. who set such questions should be summarily executed after the exam, preferably in front of their entire college. The students who suffered the exam should form the firing squad. I accept that this is a minority view and would be illegal ATM, but I want it! – Martin James Feb 06 '18 at 10:21

4 Answers4

4

The question is similar to Why are these constructs (using ++) undefined behavior in C? although not an exact duplicate due to the (subtle) sequence point inside the ?: operator.

There is no predictable output since the program contains undefined behavior.

While the sub-expression ++*p is sequenced in a well-defined way compared to *p because of the internal sequence point of the ?: operator, this is not true for the other combinations of sub-expressions. Most notably, the order of evaluation of the operands to = is not specified:

C11 6.5.15/3:

The evaluations of the operands are unsequenced.

*p++ is not sequenced in relation to ++*p. The order of evaluation of the sub-expressions is unspecified, and since there are multiple unsequenced side-effects on the same variable, the behavior is undefined.

Similarly, *p++ is not sequenced in relation to *p. This also leads to undefined behavior.

Summary: the code is broken and full of bugs. Anything can happen. Whoever gave you the assignment is incompetent.

Lundin
  • 195,001
  • 40
  • 254
  • 396
3

at the beginning i want to understand what the first for statement doing

This is what one would call code obfuscation... The difficult part is obviously this one:

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

OK, we convert p to a pointer to char, then compare it to another pointer retrieved from array a that points to the first element past a: sizeof(int)*NUM is the size of the array - which we could have gotten much more easily by just having sizeof(a), so (char*)p < (char*)a + sizeof(a)

Be aware that comparing pointers other than with (in-)equality is undefined behaviour if the pointers do not point into the same array or one past the end of the latter (they do, in this example, though).

Typically, one would have this comparison as p < a + sizeof(a)/sizeof(*a) (or sizeof(a)/sizeof(a[0]), if you prefer).

*p++ increments the pointer and dereferences it afterwards, it is short for p = p + 1; *p = .... ++*p, on the other hand first dereferences the pointer and increments the value it is pointing to (note the difference to *++p, yet another variant - can you get it yourself?), i. e. it is equivalent to *p = *p + 1.

The entire line *p++ = ++*p<60 ? *p : 0; then shall do the following:

  • increment the value of *p
  • if the result is less than 60, use it, otherwise use 0
  • assign this to *p
  • increment p

However, this is undefined behaviour as there is no sequence point in between read and write access of p; you do not know if the left or the right side of the assignment is evaluated first, in the former case we would assign a[i] = ++a[i + 1], in the latter case, a[i] = ++a[i]! You might have gotten different output with another compiler!!!

However, these are only the two most likely outputs – actually, if falling into undefined behaviour, anything might happen, the compiler might just to ignore the piece of code in question, decide not to do anything at all (just exit from main right as the first instruction), the program might crash or it could even switch off the sun...

Be aware that one single location with undefined behaviour results in the whole program itself having undefined behaviour!

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
0

The only way to really understand this kind of stuff (memory management, pointer behaviour, etc.) is to experiment yourself. Anyway, I smell someone is trying to seem clever fooling students, so I will try to clarify a few things.

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

This structure in memory would be something like:

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

This creates a vector of five int, so far, so good. The obvious move, given that data, is to run over the elements of a using p. You would do the following:

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

However, the first change to notice is that both loops convert the pointers to char. So, they would be:

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

Instead of pointing to a with a pointer to int the code converts pto a pointer to char. Say your machine is a 32bit one. Then an int will probably occupy four bytes. With p being a pointer to int, when you do ++p then you effectively go to the next element in a, since transparently your compiler will jump four bytes. If you convert the int pointer to a char instead, then you cannot add NUM and assume that you are the end of the array anymore: a char is stored in one byte, so ((char *)p) + 5 will point to the second byte in the second element of a, provided it was pointing at the beginning of a before. That is way you have to call sizeof(int) and multiply it by NUM, in order to get the end of the array.

And finally, the infamous *p++ = ++*p<60 ? *p : 0;. This is something unfair to face students with, since as others have already pointed out, the behaviour of that code is undefined. Lets go expression by expression.

++*p means "access p and add 1 to the result. If p is pointing to the first position of a, then the result would be 21. ++*pnot only returns 21, but also stored 21 in memory in the place where you had 20. If you were only to return 21, you would write; *p + 1.

++*p<60 ? *p : 0 means "if the result of permanently adding 1 to the value pointed by p is less than 60, then return that result, otherwise return 0.

*p++ = x means "store the value of x in the memory address pointed by p, and then increment p. That's why you don't find ++p or p++ in the increment part of the for loop.

Now about the whole instruction (*p++ = ++*p<60 ? *p : 0;), it is undefined behaviour (check @Lundin's answer for more details). In summary, the most obvious problem is that you don't know which part (the left or the right one), around the assignment operator, is going to be evaluated first. You don't even know how the subexpressions in the expression at the right of the assignment operator are going to be evaluated (which order).

How could you fix this? It would be actually be very simple:

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

And much more readable. Even better:

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

Hope this helps.

Baltasarq
  • 12,014
  • 3
  • 38
  • 57
  • 3
    You cannot "understand the outcome of undefined behavior" by experimenting, as the result may be any behavior. The behavior is not guaranteed to be deterministic nor repeatable. This is not as easy to dismiss as just an issue of unspecified order of evaluation, it goes deeper than that. That is, the problem isn't that the programmer can't know the order, the problem is that _the compiler_ can't know the order. – Lundin Feb 06 '18 at 09:54
  • I've written about a lof of things in my answer, apart of the undefined behaviour, which I mention at the very end. Anyway, I've made a clarification of that first sentence. – Baltasarq Feb 06 '18 at 09:59
  • 2
    "The problem is, you don't know which part (the left or the right one) around the assignment operator is going to be evaluated first, so the results could vary in different compilers." Specifically this, is only the top of the undefined behavior iceberg. If the only issue was unspecified order of evaluation, then at least the code would behave deterministically, in some unspecified way. But it wouldn't crash and burn. – Lundin Feb 06 '18 at 10:00
  • @Lundin, I suggest you write your own answer if you feel so disappointed with mines. Thank you anyway. – Baltasarq Feb 06 '18 at 10:29
  • I already did, but I think it is important to point out the difference between unspecified order of evaluation and plain undefined behavior. The former could either not be an issue at all, or alternatively lead to unexpected/unpredictable results. The latter can crash the whole program. Apart from this, your answer is fine. – Lundin Feb 06 '18 at 10:39
0

Short answer: because of this line

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

it is impossible to say how the program behaves. When we access *p on the right-hand side, does it use the old or the new value of p, that is, before or after the p++ on the left-hand side gets to it? There is no rule in C to tell us. What there is instead is a rule that says that for this reason the code is undefined.

Unfortunately the person setting the question didn't understand this, thinks that "tricky" code line this is something to make a puzzle about, instead of something to be avoided at all costs.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103