8 ms·
Int a = 5; a = a++ + ++a; a =? (2011)
- nDRDY 5mo agoOh god. How long before yet another UB-based question ends up in technical coding interviews?
- nananana9 5mo agoThe nice thing about these is that all answers are correct.
- jbxntuehineoh 5mo agothe correct answer is that the program will launch nethack, duh
- gynvael 5mo agoHaha this comment is spot on :)
- Tomte 5mo agoSome C++ quiz with ++a and a++? It‘s always about sequence points, or better the lack of sequence points. It‘s the standard technical C++ blog post everybody seems to write.
- magicalhippo 5mo agoPerhaps I'm just naive and/or have forgotten too much C, not that I knew that much, but I'm a bit perplexed as to why this is UB. It seems like something that should trigger a "we should specify this" reaction when adding these operators, and there is at least one reasonable way to define it which is fairly trivial and easily implementable.
- gynvael 5mo agoYeah, like left-to-right as in JS for example.
- susam 5mo agoThe code in the post seems very similar to the one in my own post from 2010: https://susam.net/sequence-points.html https://susam.net/sequence-points.html int a = 5; a += a++ + a++; I do remember that this particular code snippet (with a = 5, even) used to be popular as an interview question. I found such questions quite annoying because most interviewers who posed them seemed to believe that whatever output they saw with their compiler version was the correct answer. If you tried explaining that the code has undefined behaviour, the reactions generally ranged from mild disagreement to serious confusion. Most of them neither cared about nor understood 'undefined behaviour' or 'sequence points'. I remember one particular interviewer who, after I explained that this was undefined behaviour and why, listened patiently to me and then explained to me that the correct answer was 17, because the two post-increments leave the variable as 6, so adding 6 twice to the original 5 gives 17. I am very glad these types of interview questions have become less prevalent these days. They have, right? Right?
- colechristensen 5mo ago>I am very glad these types of interview questions have become less prevalent these days. They have, right? Right? I just refuse to do interviews like that any more.
- kentm 5mo agoIMO, The only reasonable answer if asked this in an interview is “I would not write code where I have to know the answer to this question” These sorts of things are neat trivia to learn about things like sequence points but 99.9% of the time if it matters in your codebase you're writing something unmaintainable.
- tzs 5mo ago> IMO, The only reasonable answer if asked this in an interview is “I would not write code where I have to know the answer to this question” That's half of a reasonable answer. The other half is "but I do know the answer so if I see it when reviewing or working on someone else's code I can flag it or rewrite it, and explain to them why it is bad".
- HelloNurse 5mo agoThe final value of a is that if you write this you are fired. It's worse than a racist joke.
- Aardwolf 5mo agoWhat's the reason that C didn't define the order of this? The horrible undefined behavior of signed integer overflow at least can be explained by the fact that multiple CPU architectures handling those differently existed (though the fact that C even 'attracts' its ill-defined signed integers when you're using unsigned ones by returning a signed int when left shifting an uint16_t by an uint16_t for example is not as forgivable imho) But this here is something that could be completely defined at the language level, there's nothing CPU dependent here, they could have simply stated in the language specification that e.g. the order of execution of statements is from left to right (and/or other rules like post increment happens after the full statement is finished for example, my point is not whether the rule I type here is complete enough or not but that the language designers could have made it completely defined).
- rramadass 5mo ago> What's the reason that C didn't define the order of this? The OP article provides experimental details but annoyingly does not give the big picture w.r.t. C language specifications (provided in the links though). There are three concepts at interplay here which is at the root of the problem; 1) Expressions (evaluates to a single value) 2) Statements (tells computer to perform an action) and 3) Sequence Points (specific moment during execution when all previous side-effects are guaranteed to be complete). It is the sequence points during the evaluation of expressions which is important to understand here. From https://en.wikipedia.org/wiki/Sequence_point https://en.wikipedia.org/wiki/Sequence_point; In C and C++, a sequence point defines any point in a computer program’s execution at which it is guaranteed that all side effects of previous evaluations will have been performed, and no side effects from subsequent evaluations have yet been performed. They are a core concept for determining the validity of and, if valid, the possible results of expressions... 1) An expression's evaluation can be "sequenced before" the evaluation of another expression. (Equivalently, the other expression's evaluation can be "sequenced after" that of the first.) 2) The expression's evaluation is "indeterminately sequenced", meaning that one is "sequenced before" the other, but which is unspecified. 3) The expression's evaluation is "unsequenced", meaning the operations in each expression may be interleaved. The "Order of Evaluation" states; (from https://en.cppreference.com/c/language/eval_order https://en.cppreference.com/c/language/eval_order) "Order of evaluation of the operands of any C operator, including the order of evaluation of function arguments in a function-call expression, and the order of evaluation of the subexpressions within any expression is unspecified (except where noted below)." The "Single Update Rule" states; (from https://www.accellera.org/images/eda/sv-bc/0282.html https://www.accellera.org/images/eda/sv-bc/0282.html) Between consecutive "sequence points" an object's value can be modified only once by an expression. The C language defines the following sequence points: Left operand of the logical-AND operator (&&). Left operand of the logical-OR operator (||). Left operand of the comma operator. Function-call operator. First operand of the conditional operator. The end of a full initialization expression. The expression in an expression statement. The controlling expression in a selection (if or switch) statement. The controlling expression of a while or do statement. Each of the three expressions of a for statement. The expression in a return statement. Putting all of the above together in OP's code snippet; The "single update rule" fails for the expression since the variable a is modified multiple times between two consecutive sequence points and hence the result is UB. For more detailed explanations, see Angelika Langer's Sequence Points and Expression Evaluation in C++ - https://angelikalanger.com/Articles/VSJ/SequencePoints/SequencePoints.html https://angelikalanger.com/Articles/VSJ/SequencePoints/Seque...
- vishnugupta 5mo agoI am, thankfully, out of this craziness now but it was fun solving ton of such puzzles from Yashavant Kanetkar books while preparing for campus hiring interviews back in 2000. "Test Your C Skills" in particular. Fun times. https://www.scribd.com/document/235004757/Test-Your-C-Skills-Yashwant-Kanetkar-OCRd https://www.scribd.com/document/235004757/Test-Your-C-Skills...
- _kst_ 5mo ago"Test Your C Skills" is a published book by Yashavant Kanetkar, apparently published in 2005, and still available in paperback. The document you linked to appears to be a scan of a printed copy of that book, and is almost certainly in violation of copyright. The cover and the title and copyright pages are notably missing.
- Dylan16807 5mo agoThat's worth being aware of, though somewhere around 20 years is where I start caring a lot less about copyright from a moral or practical point of view. Yeah, that book stays out of the public domain until the next century, but it shouldn't.
- vishnugupta 5mo ago> apparently published in 2005 No. It was published in late 90s. As per this copy on Archive.org 1997 https://archive.org/details/testyourcskills00yash https://archive.org/details/testyourcskills00yash
- nothrowaways 5mo ago12
- Someone 5mo ago> The interesting thing here is the Undefined Behavior (UB), well... actually two UBs, thanks to which there are three possible correct answers: 11, 12 and 13. There’s UB, so any answer is possible, isn’t it?
- gynvael 5mo agoHey! Author here :) I'm going top-to-bottom through comments, and there was a similar question, so I'll link my answer here: https://news.ycombinator.com/item?id=48140821 https://news.ycombinator.com/item?id=48140821 (TL;DR: you are right, but there's another perspective on this)
- Boxxed 5mo ago> The interesting thing here is the Undefined Behavior (UB), well... actually two UBs, thanks to which there are three possible correct answers: 11, 12 and 13. No, if you invoke undefined behavior any result at all is possible.
- bombcar 5mo agoI feel we need another category - unspecified behavior. I think everyone would agree the compiler should putout ONE of those answers and that nasal demons would be out of spec. The problem is that it’s not specified which should be picked, but all pick something.
- fc417fc802 5mo agoI agree what you say seems reasonable at a glance. But (IIUC) the issue is that for optimization we want the compiler to assume that UB doesn't happen in order to constrain the possible code paths. So if it goes some distance down a possible execution branch and discovers UB it can trim the subtree. At that point "anything can happen" becomes an (approximate) reality. The obvious counterpoint in this particular instance is that there's no good reason not to make such an awful expression a compile time error. I also personally think that evaluation order should be strictly defined. I'm unclear if the current arrangement ever offers noticable benefits but it is abundantly clear that it makes the language more difficult to reason about.
- IshKebab 5mo agoAs I understand it UB was not really intended to be for optimisation. It was so that C could compile on wildly different architectures that existed at the time. Today we don't have nearly the variety of architectures, so they in theory C doesn't need nearly as much UB (like more modern languages). Although there is one modern case where C's "anything goes" attitude has actually helped: CHERI works pretty well with C/C++ even though pointers are double the size they normally are, because doing so many things with pointers is UB (I assume because of segmented memory). CHERI is a slightly awkward target for Rust because Rust makes more assumptions about pointers - specifically that pointers and addresses are the same size.
- phendrenad2 5mo agoTried it on https://www.onlinegdb.com/online_c_compiler https://www.onlinegdb.com/online_c_compiler Returns 12. If I were designing C, it would return 13. But then again, I'm an assembly programmer.
- topspin 5mo agoGodbolt: clang and gcc compilers give 12. msvc compilers yield 13. #include <stdio.h> int main() { int a = 5; a = a++ + ++a; printf("%d\n", a); return 0; } x64 msvc v19.50 VS18.2 output: example.c ASM generation compiler returned: 0 example.c Execution build compiler returned: 0 Program returned: 0 13 x86-64 gcc 16.1 output: ASM generation compiler returned: 0 Execution build compiler returned: 0 Program returned: 0 12 armv8-a clang 22.1.0 output: <source>:5:10: warning: multiple unsequenced modifications to 'a' [-Wunsequenced] 5 | a = a++ + ++a; | ^ ~~ 1 warning generated. ASM generation compiler returned: 0 <source>:5:10: warning: multiple unsequenced modifications to 'a' [-Wunsequenced] 5 | a = a++ + ++a; | ^ ~~ 1 warning generated. Execution build compiler returned: 0 Program returned: 0 12
- ecshafer 5mo agohmm surprising. I assumed it would be 12 since 5+5+1+1 doesn't really matter what order you do it in. But I suppose this really undefined behavior.
- compiler-guy 5mo agoWith undefined behavior, a conforming compiler can do anything it wants at all, including generating a program that segfaults or something else. But what often happens in practice is that "Bill's Fly-By-Night-C-Compiler-originally-written-in-the-mid-nineties" implemented it in some specific way (probably by accident) and maintains it as a (probably informal) extension. And almost certainly has users who depend on it, and can't migrate for a myriad of reasons. Anyway, it's hard to sell an upgrade when users can't just drop the new compiler in and go. At the language level, it is undefined-behavior, and any code that relies on it is buggy at the language level, and non-portable. Defining it would make those compiler non-conforming, instead of just dependent on defining something that is undefined. Probably the best way forward is to make this an error, instead of defining it in some way. That way you don't get silent changes in behavior. Undefined behavior allows that to happen at the language level, but good implementations at least try not to break user code without warning. Modern compilers with things like UBSan and such makes changing the result of undefined behavior much less of an issue. But most UB is also, "No diagnostic required", so users don't even know they have in their code without the modern tools.
- suprjami 5mo ago> including generating a program that segfaults or something else. UB = run nethack or Emacs: https://feross.org/gcc-ownage/ https://feross.org/gcc-ownage/ We should have kept this behaviour. It would make UB a lot more unpalatable and easy to find.
- yason 5mo agoThe only point you can conclude out of these discussions, especially in an interview, that it doesn't matter what the answer happens to be on $CC and $ARCH but you wouldn't want anyone to write stuff like that in the first place. Failing to recognize the dangers would be an instant fail; knowing that something reeks of undefined behaviour, or even potential UB, is enough: you just write out explicitly what you want and skip the mind games.
- summarybot 5mo ago> If you would like to test your compiler (posting back the results in the comments is really appreciated, especially from strange/uncommon compilers and other languages which support pre- / post- increment .... Uh, 85% of them show the wrong result so 85% of them clearly do not support pre and post increment.
- _kst_ 5mo agoIf the behavior is undefined, there is no wrong result.
- summarybot 5mo agoExcept that ++a means increment first and a++ means increment after. And that's well-documented and thoroughly understood. Implying that this is undefined behavior is a cop out and cope. Preposterous and juvenile. If not implemented , it should be. Case closed.
- pplonski86 5mo agoI love such puzzles! I used to use a lot ternary operators in C++ but one day friend of mine told me that I shouldn't nest ternary operators too much because code is too complicated to read - he understands code perfectly, he was just worried about younger programmers. Since then I started to use longer versions of code instead of smart shortcuts - to improve readability of code.
- comrade1234 5mo agoThe a=13 was most surprising to me but in retrospect obvious and amusing.
- sangeeth96 5mo agoI had to fight through school and university in India with my teachers who believed these were legit questions to ask in written exams. Can't 100% blame them since almost all standard-issue textbooks had them and claimed they'd give predictable output. I thought the same until I noticed the weirdness when running them across different compilers and after I read about UB, sequence points and similar quirks in books that are not total garbage. Luckily, I ended up with smug smiles in all those cases after showing them the output from different compilers.
- afdbcreid 5mo agoBut what answer did they expect? A specific number or that it's UB?
- sangeeth96 5mo agoA specific number.
- onlyrealcuzzo 5mo agoPlease tell me the answer is somehow 42! int a = 5; a = (++a * a++) + --a; a = ?
- tombert 5mo agoI have always hated this crap; the fact that I'm not 100% sure the result of this indicates that maybe the ++ operator (pre or postfix) is something that should be avoided? I don't do a lot of C anymore, but even when I did, I always would do increments on separate lines, and I would do a +=1, or just a = a + 1. I never noticed a performance degradation, and I also don't think my code was harder to read. In fact I think it was easier since I think the semantics were less ambiguous.
- amavect 5mo agoI also started doing this. I feel that "b = expr(a); a++;" expresses what I mean better than "b = expr(a++)": store expr(a) in b, then store a+1 in a. Any good compiler will optimize the same. After separating a++ onto its own line, replacing a++ with a+=1 or a=a+1 comes down to personal taste in syntax sugar. I vote for a+=1.
- tombert 5mo agoYeah exactly, especially for newer people. I wouldn't be surprised if someone read `b = expr(a++)` to indicate that `a` is incremented, and then passed into `expr`, especially considering that it is within parentheses. The fact that it does it after passing it in is weird, and not obvious, at least not in my opinion. In my mind, there's no reason not to do what you suggested, or do the increment of `a` on the line before if you want the prefix.
- p0w3n3d 5mo agoOn my CS lectures algorithms professor used this pseudo language when writing an algorithm on a whiteboard : I <- I++ On the next hour another professor was giving lecture on C++ programming. I asked him the question: what would happen if we compiled i = i++ He went into some deep elaboration on it, but reassumed that only idiot would write like this...
- mywittyname 5mo agoOut of curiosity, I checked if gcc would optimize i = i++ out, and it does!
- _kst_ 5mo agoWhat can be optimized out depends on the context. If you write: int i = 0; i = i++; and never use the value of i, the declaration and assignment are likely to be optimized out. (The behavior of the assignment is undefined, so this is a valid choice). If you print the value of i, the compiler can still optimize away the computation, but is perhaps less likely to do so. The solution, of course, is not to write code like that. Decide what you want to do, and write code that does that. "i = i++" will never be the answer to "how do I do this?", and wouldn't be even if the behavior were well defined. If you want i to be 1, write "int i = 1;".
- syngrog66 5mo agoThe smart nerd will know precisely how to decode that line's results. The wise nerd will not allow lines like it in their codebase, in the first place and, having seen one, will refactor it (probably involving more lines or parentheses) to make it more clear and easier to maintain. The latter approach scales better, in long run.
- gynvael 5mo agoThis is true. What's also true, is that if that smart name works in cybersec, they'll feel right at home :) (this is related to my other comment here https://news.ycombinator.com/item?id=48140821 https://news.ycombinator.com/item?id=48140821)
- nnm 5mo ago++ should be banned, just like goto
- _kst_ 5mo agoAre you volunteering to update all the code that would be broken?
- Timwi 5mo agoThe statement is valid C#, which has left-to-right execution order and no undefined behavior. The answer is 5 + 7 = 12.
- chasil 5mo agoAwk also says it's 12. awk 'BEGIN{a=5; a = a++ + ++a; print a}' 12
- kencausey 5mo agohttps://www.gnu.org/software/gawk/manual/gawk.html#index-precedence https://www.gnu.org/software/gawk/manual/gawk.html#index-pre... "When side effects happen is implementation-defined. In other words, it is up to the particular version of awk."
- chasil 5mo agoAndroid appears to use the One True AWK. :/ $ awk 'BEGIN{a=5; a = a++ + ++a; print a}' 12 :/ $ which awk /system/bin/awk :/ $ awk --version awk version 20240728
- adverbly 5mo ago/sarcastic This is how to keep simpletons out of your code base. Every numeric constant is defined in terms of a different lang quiz. Works well in JS as well of course. const DEFAULT_SELECTION = true + true const BASE_PRICE = 4 * parseInt(0.0000001) const BILLING_DAY_OF_MONTH = a++ + ++a
- dhosek 5mo agoI don’t have gcc available so I can’t test it, but I wonder what it does with int a = 5; int b = a++; if it gives b==5 in this circumstance (which I would say is the correct value), then it seems that giving 13 for a++ + ++a is a bug in the compiler. I kind of feel like giving 6 as an answer would also be a bug in the compiler since postfix-++ should return the old value and then increment.
- deleted 5mo ago[deleted]
- compiler-guy 5mo agoThe trick here is that the original expression contains undefined behavior. Your example does not.
- _kst_ 5mo agoYour code: int a = 5; int b = a++; has well defined behavior. The first line initializes a to 5. The second initializes b to 5 and sets a to 6. (The language doesn't specify the order of the two operations of assigning a value to be and incrementing a, but in this case it doesn't matter.) Giving 13 for a++ + ++a is not a bug in the compiler. It's a bug in the code. The correct answer to "what does a++ + ++a do" is "it gets rejected in code review and replaced with code that expresses the actual intent.
- danbruc 5mo agoMy expectation was none of the four presented. Evaluate left to right, a is five, post-increment, pre-increment, a is seven, 5 + 7 = 12. For right to left I would expect pre-increment, a is six, a is still six, post-increment, a is 7, overwrite with 6 + 6 = 12.
- deleted 5mo ago[deleted]
- deepsun 5mo agoWhy do you need Java four times in tests? They are all the same. The main, I would say, defining, feature of Java is "no of undefined behavior". Aka "write once, run everywhere".
- taylodl 5mo agoThe trait of an experienced C developer is to avoid creating expressions such as this.
- benmmurphy 5mo agohackernews capitalising Int makes this question kind of confusing. Because the question is meant to be about c++ behaviour but `Int` is not a standard c++ type. But Int is not java because java uses Integer and `Int` is not c# because c# has uses the explicit IntBitsize types. I think maybe the only languages where the title makes sense in is Scala or Swift.
- hyunwoo222 5mo ago[flagged]
- ripe 5mo agoFor understanding this type of question, I highly recommend the C FAQ compiled by Steve Summit based on Usenet discussions in the comp.lang.c newsgroup. You should start here: https://c-faq.com/expr/evalorder2.html https://c-faq.com/expr/evalorder2.html I cannot recommend the C FAQ enough. It is written in an accessible way and contains proper references to textbooks and standards. Disclosure: I was one of the contributors.
- nick238 5mo agoI still don't understand why programmers seemed to get off on this sort of shit. Doing `while((dest++ = src++));` is great and all (maybe fine because it's kinda idiomatic now, but should you really be using that over `strncpy`?), but being clever like that in real code makes it harder to review, and harder to understand months down the line. I've mentally cussed out 'whoever wrote this confusing shit' to only `git blame` myself.
- iamcreasy 5mo agoDoes anyone know if these are already explained in Eskil Steenberg's dependablec.org?
- sabretooth1405 5mo agoAh the kind of undefined behaviour questions that incompetent Indian professors ask to assert their dominance on poor freshman.
- stevefan1999 5mo agoAs a guy who writes compiler the answer for this is it depends on the type of parser you are using. LL and LR parser generates different derivation, and as such it is deterministically non-deterministic, hence UB.
- daemin 5mo agoBut you can still change the parser to output the expression in the AST (or otherwise) so it is evaluated left to right or right to left. Just that doing it in a way that is not natural for the algorithm will require extra code.
- koliber 5mo agoIf a behavior is undefined, the theoretical answer to this could be anything, including -123, 500, or 0. We are just lucky that the compilers choose a more sane version of undefined behavior in practice.
- kazinator 5mo agoThere is a possibility that the two increments, as well as the assignment, happen in parallel, and conflict at the bit level, resulting in a value that is neither a + 1, nor a + 2. (When I say "possibility", I meant that it would not be nonconforming, not that I have in mind a specific implementation where such a result can be reproduced.) A side-effected object may be modified at most once in one evaluation phase. But this problem already occurs in something simpler like: b = a++ + a where the problem is that a modified object is observed by a subexpression in the same evaluation phase, but that subexpression is independent of the side effect. If a is updated in some piecewise, non-atomic way, then it's possible that the right side of the + obtains a half-baked snapshot. Say that a is unsigned and wraps from FF..FF to 00..00, but say this happens byte by byte. The right side of the assignment could access a torn value like FF..00.
- Panzerschrek 5mo agoThat's why allowing ++, = and += in expressions is a language design mistake. They should be statements with no possibility of result reuse. The same is for = in branch conditions and loops.
- tialaramex 5mo agoAlthough Rust doesn't have either ++ increment operator, it does have both the assignment = and the add-assign += and they're both expressions because almost everything in Rust is an expression. Crucially however these expressions have the unit type () aka the empty tuple as their result, ruling out hard to follow C like int a = 1 + (b = 2 + (c = d * 3)); Similarly the "Oops I wrote = instead of ==" gets so rare as to be negligible when you stop coercing everything to a boolean. In Rust only true is true and only false is false. So when you write if k = 5 rather than checking if k == 5 that's a type error. The expression k == 5 is a boolean, that would be fine, but the expression k = 5 is just () and that's neither true nor false it's just the wrong type.
- ghtbircshotbe 5mo agoOne of the things I like about Python is the lack of the increment operator.
- baud9600 5mo agoI was hoping this article would conclude with, “and the C language spec in K&R says THIS which is the correct answer”. Apparently not. So the appendix in K&R is ambiguous? And yet we use ++ so often! I can see people crawling the Linux source tree using LLM-bots looking for bad uses of ++ …