Elektrine lite

← Feed

@slomo@toot.cat

Post #2013050

2023-03-17 18:25 UTC

PSA: Did you know that function argument evaluation order in C is undefined, and gcc / clang are actually doing the opposite of each other? I did, but that knowledge didn't make today's debugging session much shorter and less puzzling. The starting point was that the same code worked fine on Linux but doesn't work on macOS. Nothing system-specific anywhere near the error. As it turns out, somewhere deep down there was some code relying on a specific argument evaluation order. Something in the shape of copy(reader_read_data(r, reader_get_remaining(r)), reader_get_remaining(r)); With gcc this copied the remaining amount of data, with clang this silently copied nothing. gcc is evaluating arguments right-to-left, clang left-to-right. So clang was first advancing the cursor to the end, and then the remaining amount of data was actually 0. Should you write such code? No. Does it make sense for the code to behave differently with different, well-behaving compilers? Also no. The code in question did not cause any compiler warnings, and no static code analyser I tried (including coverity) detected it either. Fortunately another thing Rust got right: argument evaluation is explicitly defined as left-to-right. And this is just one of the parts of C with undefined, unspecified or implementation-defined behaviour. Far too many footguns to keep in mind to realistically avoid having one or another showing up somewhere sooner or later. #C #Programming #Bug #FootGun #RustLang

Replies (17)

  • @slomo@toot.cat 2023-03-17 19:58

    Also, this is not only the case for function argument evaluation order but similarly applies to the order in which operands are evaluated (except for &&, ||, comma operator and trigraph). For example gcc seems to evaluate a+b from left-to-right while MSVC evaluates it (usually?) from right-to-left.

    Open ##2598463

  • @ebassi@mastodon.social 2023-03-17 19:19

    @slomo@toot.cat That's why people should not be playing golf with C…

    Open ##2598464

  • @marc@metalhead.club 2023-03-17 19:46

    @slomo@toot.cat that’s the kind of bug you don’t want to have in your code. Undefined behavior is from the deepest depths of hell. Predictably is key to stable code. Too bad, that no static code analysis did find this bug. Even after many years of C/C++ I have never heard about this problem.

    Open ##2598472

  • @c0dec0dec0de@hachyderm.io 2023-03-17 19:59

    @slomo@toot.cat good old sequence point errors.

    Open ##2598479

  • @refi64@refi64.social 2023-03-17 21:48

    @slomo@toot.cat A while back I accidentally did this in flatpak-portal by using a pointer and then putting g_steal_pointer after it in an argument list...tested locally with Clang, everything works, code is *released in distro packages*, get a bug report about a segfault, etc etc (Iirc clang-tidy will at least warn about this in C++ when it's because of a move constructor invocation?)

    Open ##2598486

  • @vikxin@vulpine.club 2023-03-17 21:54

    @slomo@toot.cat @KitRedgrave@vulpine.club oh god, sequence points in C are such a mess

    Open ##2598488

  • @LinqLover@norden.social 2023-03-18 12:13

    @slomo@toot.cat Wow, I really hope that a good linter will flag the use of such ambivalent patterns

    Open ##2598489

  • @flameeyes@mastodon.social 2023-03-18 17:39

    @slomo@toot.cat I did, but only because I remember reading it from caleb[at]gentoo's blog a ton of years ago in the context of a Qt bug.

    Open ##2598490

  • @cr@chaos.social 2023-03-18 17:55

    @slomo@toot.cat learned that the hard way when dynamically linking DLLs in Linux applications and passing function pointers back and forth

    Open ##2598491

  • @slomo@toot.cat 2023-03-18 19:50

    @cdpjenkins@mastodon.cloud That one is actually even worse because you have multiple modifications of the same value there. This case is not just unspecified but explicitly undefined behaviour AFAIK.

    Open ##2598492

  • @ohunt@mastodon.social 2023-03-18 20:43

    @slomo@toot.cat more obnoxious - at least in the past the order of evaluation could change depending on optimization level. I recall being really confused debugging some code years ago because of this particular bit of unnecessarily UB behaviour letting the compiler change evaluation order essentially at will.

    Open ##2598493

  • @Wlm@mastodon.gamedev.place 2023-03-18 20:47

    @slomo@toot.cat Unsurprisingly (well, I had no idea really and had to check) this goes for C++ too. Which interests/worries me professionally. Source: https://en.cppreference.com/w/cpp/language/eval_order

    Open ##2598494

  • @slomo@toot.cat @mostalive@mastodon.social ISTR hitting a similar issue with some C++ code ~20 years ago because there was no sequence point around = and I had a std::map used in some caching code where the [] on the left of = was evaluated before a cache lookup on the right of it. (Can’t remember the two compilers.)

    Open ##2598495

  • @slomo@toot.cat > Fortunately another thing Rust got right: argument evaluation is explicitly defined as left-to-right. Did it, though? Sounds like the right thing to do here is to keep the order unspecified and have your compiler warn if you try to rely on it

    Open ##2598496

  • @suetanvil@freeradical.zone 2023-03-19 13:10

    @slomo@toot.cat The reason for this and a lot of other of C's wacky behaviours is that it has to be able to produce efficient code for any of a wide variety of weird hardware. The $0.06 microcontroller or $2 DSP that massively benefit from one order over the other may both need to run the same library. (There's probably a good use case for a standardized subset of C for boring computers that standardizes this kind of stuff.)

    Open ##2598521

  • @varx@cybersecurity.theater 2023-03-19 13:15

    @slomo@toot.cat IIRC, Scheme left argument eval order unspecified, but only a few of the implementations had the guts to make it unpredictable. Chaotic good.

    Open ##2598522

  • @slomo@toot.cat 2023-03-24 10:30

    @xakon@ieji.de Both were x86-64 and it seems to be the same for all targets for the given compiler. Just guessing, but the reason for this is probably that gcc was historically first used widely on ancient x86 with stack-based argument passing, and changing that now doesn't have much advantage but has the chance of breaking someone's code in a hard to debug way.

    Open ##2598523