4 ms·
Another undefined behaviour error: We had a macro BAG which looked like: #define BAG(x) **x and people would write things like: BAG(x) = f(y); Now,
by CJefferson 7y ago
Another undefined behaviour error:
We had a macro BAG which looked like:
#define BAG(x) **x
and people would write things like:
BAG(x) = f(y);
Now, we had a moving garbage collector, which could cause the value of x to change, so it was important that this was executed "as if".
temp = f(y); **x = temp;
For many years, this was exactly what gcc always did, but this wasn't required by C. An update to gcc caused, when optimisation was high enough and gcc felt like it, to instead first dereferencing x, and then calculating f(y), then assigning f(y) to the old value of x.
But, this would only cause a problem when calling f(y) caused a GC which would change x, and then a random memory location was written to.
- ckaygusu 7y agoThank you for providing my daily WTF moment. Do you know how this was tracked down?
- exikyut 7y agohttps://news.ycombinator.com/item?id=21286284 https://news.ycombinator.com/item?id=21286284
- exikyut 7y agoHi, I understand C to the extent I can write simple, intermediate-scale useful programs, but my foundational grasp of the basics is still sufficiently shaky/incomplete that I can still get easily thrown by simple memory management constructs in practice. In this case, I academically understand pointer-to-pointer references... but I'm honestly not able to mentally model the above. First of all, congratulations - you have posted comment #4,294,967,296 to be bitten by the asterisks-are-only-for-formatting-and-all-other-uses-should-be-passive-aggressively-ignored bug in the Arc software powering this site! Your prize is that the explanatory denotations of "* x" in your post have disappeared and everything between the two points you've added asterisks has become italicized instead. (It would be great if someone can figure out a way to successfully convince the admins to just fix this bug already - it has existed for the entirety of this site's existence. </rant>) With that over... I don't understand the relevance of the 3rd code block. I get the impression it somehow replaced the first one (...?), but I can't figure out how the #define would have been rewritten without changing the signature to something like #define BAG(x, y) temp = y; **x = temp; which was used like BAG(x, f(y)) but the above is not valid code as the type of `temp` is not defined or inferrable. Next, as I noted above the pointer-to-pointer thing is throwing me for a pretty big loop :) and I don't understand how a double dereference would (in practice) translate to a moving garbage collector. Like pointers I also have a basic academic understanding of GC, but sorely little practical real-world experience with either to be able to make the connection between the two ideas. Insight appreciated in advance :)
- desertrider12 7y agoTo do star star x = f(y), star x and f(y) each need to be evaluated. You'd think the right-hand side would always be evaluated before the left-hand, but that's not always possible (what should "arr[i] = i++" do? I stole this from wikipedia btw). In the above case, f(y) has this kind of side effect that change x, which is undefined. Rather than changing the macro, this could be fixed easily by putting the assignment into its own function. Then the argument (f(y)) must be fully evaluated before star x. Edit: I have no idea how to escape asterisk on HN :(
- CJefferson 7y agoThe problem with putting the left hand side in a function is you can't assign to the result of a function in C, so it has to be a macro. (although our actual general fix was "stop trying to put anything fancy on the left hand side of an assignment")
- tom_mellior 7y ago> I have no idea how to escape asterisk on HN Put a space after it: "* x = f(* y)"
- CJefferson 7y agoSo, the double pointer dereference is to handle moving memory with C. The idea is we can't (reasonably) change every pointer to a piece of memory (as in raw C the compiler can hide pointers, and we scan the stack, so we don't know if things we find there are pointers or integers). So, x points to a well knowm place, and that contains a pointer to the place where the data is stored. When we GC, we change where the data is stored, and also change x to point to the new location. Then anyone with x can use x to find the new location of the memory. This does mean every memory access goes through this "lookup" pointers, and those lookup pointers can't move. In practice the fix was "tell people not to put BAG on the left hand side of assignments", rather than change this macro which occurs everywhere (although we are also slowly phasing it out)
- Someone 7y ago”but I can't figure out how the #define would have been rewritten without changing the signature” It likely is possible in C++, when combined with a generic class that wraps x and defers dereferencing it to the time its copy assignment operator runs. BAG(x) would return an instance of that class. For completeness, you also would want an implicit conversion to the type of **x in that class, so that foo(BAG(x)=f(y)) or while(BAG(x)=f(y)) would continue to work