- Feature Articles
- CodeSOD
- Error'd
- Forums
-
Other Articles
- Random Article
- Other Series
- Alex's Soapbox
- Announcements
- Best of…
- Best of Email
- Best of the Sidebar
- Bring Your Own Code
- Coded Smorgasbord
- Mandatory Fun Day
- Off Topic
- Representative Line
- News Roundup
- Editor's Soapbox
- Software on the Rocks
- Souvenir Potpourri
- Sponsor Post
- Tales from the Interview
- The Daily WTF: Live
- Virtudyne
Admin
++zer0th
Admin
Honestly I don't hate this. It looks like code written by a relative beginner who couldn't spot his own error and yet managed to figure out a fix eventually. Is it good code? Hell no. But it is code by someone who at the very least tried hard. And I see potential in that. Like if I was grading a class in programming 101 I would pass his.
And it's not like this is something seriously written by a professional for production. Right? Right?
Admin
Sorry no, I don't care how much of a beginner the perpetrator of this is. I would definitely dock marks if it came to me for evaluation.
I might even use it as an exercise in a programming 101 class as a "What's wrong with this picture?" example.
Admin
I hope this isn't C++ (earlier versions for sure, but C++17 changed the rules in ways that might exclude certain parts of this analysis), because if it is, it is a Glastonbury-scale festival of undefined behaviour.
i = i++;
modifiesi
twice, and might, if the compiler is feeling malicious, be equivalent toi++
.The order in which the arguments to
map.put()
are evaluated is not specified by the standards, so it's not clear whether (ifi
is initially 1) we pass item 1 and item 2, or item 2 and item 2. (Relying on the order causes UB.)Admin
"It's like ChatGPT, but less self-aware."
ChatGPT has zero awareness, so while this developer has seemingly close to that it's hard to imagine it's actually less.
Admin
Actually,
i= i++;
is guaranteed to be a no-op. According to N1570 (the C11 final draft), 6.5.16 § 3:So
i++
evaluates toi
pre-increment, and has the side effect of incrementingi
; but theni
is assigned back the original value (and that's well-defined because of the above clause).The
map.put()
line is however a classical example of unsequenced evaluation, but that is "only" unspecified, not undefined (according to my reading of 5.1.2.3 § 3, anyway... god this standard is convoluted. :/). Not that this makes much difference.Still, this is something I'd only see having a place in the Underhanded C Contest, not quiiiite in production :)
Admin
I personally do hate this, because it manages to trigger undefined (or at least unspecified, c.f. my other comment) behaviour in one place, and only narrowly dodge it in a second one. C requires a lot of discipline to avoid triggering UB (which a different compiler, or a later version of the same, will eventually subtly break, much to everyone's confusion and dismay), so I wouldn't pass this code even in a CS 101 course.
Of course, TRWTF may be the C standard committee's reluctance to adapt the language's semantics (hey, it only took them a decade or two to stop supporting one's complement and sign-magnitude! And only one more to get rid of pre-standard function declarations!), and/or using C to teach CS 101.
...would now be a good time to shill Rust? 😁
Admin
That code looks like it shouldn't work as described. If I was doing a code review on that, I'd either re-write it so it actually looked like how it was supposed to function or ask the developer to walk me through their logic for writing it.
Admin
There is a lot more WTF to this. It's a wonder this works at all.
map.put(values.get(i), values.get(++i)); is undefined behaviour, because the compiler is free to order the evaluation of parameters. It may increment i before it gets passed to values.get(i), or it may not. Thus, it's basically bound to a specific compiler. Have fun tracking down the bugs when changing compiler versions.
Admin
This is highly unlikely to be C code. It technically could be, if
map
were a structure containing a function pointer namedput
, but that would be highly unusual given that it doesn't passmap
as a parameter, so it'd have to be a global mapping object.It could be C++, but more likely it's Java, using the standard
Map
interface. And in Java, the expressioni = i++
has well-defined behavior: it incrementsi
and returns the old value ofi
in evaluatingi++
, and then it assigns the old value ofi
to itself, thereby having no change in the value ofi
.Admin
"UB (which a different compiler, or a later version of the same, will eventually subtly break, much to everyone's confusion and dismay),"
No, that's "implementation dependent". "Undefined behavior" means it can change because of what else is running on the machine, subtle timing issues, the way you held your mouth, or for no reason at all.
Admin
I said pass, not mark highly. Like this would be a minimum points pass with a note to see me after class. But it wouldn't be a fail.
Admin
Also, I assumed this was neither C nor C++ but maybe C# or similar due to the fact that the article made no mention of it being dumb on account of UB.
This said, I absolutely do think that a persons first language should be C. The proper way to learn programming is to understand the base concepts of what you are doing. And you can only get there with low level languages.
Starting with a high level language teaches you how to put together blocks of someone elses code that you neither understand nor care to and produce results that are cargo cult implementations of "don't question it as long as it works".
And C is the ideal candidate because it is as low level as it gets while still being abstract enough that it is not hardware dependent.
Admin
ugh - I hate for loops in almost all cases. surely even in C there's some kind of functional programming library available that would give you 'partition' and 'zip'
in JavaScript this would be something like (untested):
const pairs = .unzip(.partition(values, x=> !(x%2))
Admin
That same section also states that "the evaluations of the operands are unsequenced". In addition in 6.5 paragraph 2:
"If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined."
Which suggests to me that this is still undefined.
Admin
I probably would have written (in Dart, because I don't quite know the OP language):
for (final i = 0; i < input.size; ) { final first = input[i++]; final second = input[i++]; doSomethingWithThePair(first, second); }
In this case, input[i] is invariantly "the first unprocessed item of input". And the third part of the 3-part for-loop has nothing to do. In fact, except for the local i, this could have been a while loop instead.
Addendum 2023-07-05 12:04: I've found that thinking about invariants in loops is not just an academic exercise, it's actually quite practical.
Admin
"++" always makes you have to think extra. Why not
for(int i=0; i<values.size(); i += 2)
{
}
The "+= 2" makes it clear that you are processing them two at a time.
(This is the first time I've put code in a comment here. Expect tears.)
Admin
About the easy reader comment.
I would think ChatGPT is already trained with the code from this site. It was trained with basically the whole internet as a dataset, which would include this site.
So, there already is an LLM in existence which was trained using code from this site.
Admin
The side effect you mentioned isn't part of the "value computations".
cppreference.com observes this about the C rule you cited:
https://en.cppreference.com/w/c/language/eval_order
Note: not the side-effects. They are sequenced whenever the compiler feels like making them happen, which is why
i = i++
is UB.But of course I was talking about C++, so a discussion of the fine-grained details of C is of limited relevance.
Admin
It's likely Java. Java has a
Map
type with aput
method. It's unlikely to be C#, because in .NET, maps are called dictionaries, and method names begin with an uppercase letter.Admin
Addendum 2023-07-05 13:28: Of course, the
;
should be:
else it’s not going to work … Damn :(Admin
myLoop mov cx, 0 // do stuff add cx, [increment] cmp cx, [limit] jle myLoop
xD
Admin
UB is a subset of "implementation-defined", as what a compiler does to a certain UB code path may vary across its versions. (Note further that implementations are allowed to be stricter than the standard, i.e. to define what would otherwise be undefined! GCC does this, for example, by treating
<negative value> >> n
as a sign-extending right-shift. C.f. their implementation-defined behaviour docs)The most famous example of this, I think, has to be newer versions of GCC making some "eager" pointer derefs optimising out NULL checks in the Linux kernel, which caused quite the turmoil!
Admin
Moving from BASIC to Pascal I was shocked that there was no "STEP" option in Pascal and that a for loop in Pascal only worked on ordinals. Seemed so archaic. Until I realised what other options opened up due to dropping BASIC, and yes, maybe I shouldn't use the for loop all that often.
Admin
Why am I having Deja Vu ??
I m sorting out code where one "developer" or "code monkey" would be rating them too highly, claims in code things like
Admin
Okay, always, always, always cache result values before using them in the condition of an iteration. By definition all C-style languages reevaluate the condition in an for-statement each single time, so never do something like
but instead use
The reason is pretty simple: Because by definition the value is allowed to change the compiler can't optimize this step and it will always make the call in every iteration. Not only is the call itself a waste of resources, if there is something more complex going on in this method it can be even worse up to potential deadlocks in some scenarios. So it's really, really bad practice to do so.
Admin
As mentioned numerous times the usage of i++ has some issues. But whytf is no one looking at the other part of the for loop? 'for(int i=0; i<values.size();' Inside the loop you will be accessing values out of bounds of your stop condition. Has no one here learned not to trust input or in this case input size?
Perhaps for(int i=0; ++i<values.size();i++) { would be more intuitive to use with acces to values(i-1) and values(i) resolving any ambiguous execution order of map