Skip to content

Zig's += operator evaluates differently than C's #9386

Closed as duplicate
Closed as duplicate

Description

@floooh

Not sure if this is a bug or feature, I just stumbled over this while moving some C code over to Zig and this was a bit of a head scratcher.

Basically, if you have a statement like this:

bla.val += func(&bla);

...then Zig seems to first "capture" the value of bla.val before calling func() and then doing the addition, while C first seems to call func and then perform the addition. This leads to different behaviour if func() has the side effect of also modifying bla.val.

Godbolt example in Zig:

https://www.godbolt.org/z/E6xbd3839

...and in C:

https://www.godbolt.org/z/jvvr8ne5q

The effect can be simulated in C by "unfolding" the expression (although I suspect that this actually depends on unspecified behaviour in C regarding the evaluation order):

// this behaves like Zig's +=
bla.val = bla.val + func(&bla);
// this behaves like C's +=
bla.val = func(&bla) + bla.val;

IMHO Zig should behave like C unless of course there's a reason not to, such as hidden gotchas in C that should be fixed (like the unspecified evaluation order, the less unspecified behaviour in Zig the better).

Also see this twitter thread (there's some interesting info about C# behaviour in there):

https://twitter.com/FlohOfWoe/status/1415385408771399680

Activity

  1. bfredl commented on Jul 15, 2021

    @bfredl
    Contributor

    bla.val += func(&bla);

    if func modifes bla.val isn't this a concurrent modification within the same sequence point, and thus not well defined by the C standard in the first place?

  2. floooh commented on Jul 15, 2021

    @floooh
    ContributorAuthor

    @bfredl, not sure, I'm not much of a language lawyer, but at least GCC, Clang and MSVC all agree on the same behaviour :)

  3. bfredl commented on Jul 15, 2021

    @bfredl
    Contributor

    but at least GCC, Clang and MSVC all agree on the same behaviour :)

    For all cases of the this pattern with optimization enabled or this one example in debug mode? :)

    At least the draft c99 standard says E1 += E2 should have the same semantics as E1 = E1 + E2 (after lvalue evaluation) so it is at least unspecified behavior, as you mentioned.

  4. floooh commented on Jul 15, 2021

    @floooh
    ContributorAuthor

    The C code has been working for the last few years both with optimization enabled (-Os and -O3) and disabled across GCC, Clang and MSCV and 32- and 64-bit x86, 64-bit ARM and WASM (and the highest warning levels). So even if it's unspecified in the standard, the compiler vendors seem to have agreed on one common behaviour. But I agree that it would be a good thing if such things are more clearly defined in Zig.

  5. mikdusan commented on Jul 15, 2021

    @mikdusan
    Member

    for convenience this is C2x (consistent with C17 and C11):

    image

    and this answer from stackoverflow

  6. floooh commented on Jul 16, 2021

    @floooh
    ContributorAuthor

    Would probably make sense to also look in detail at C's += specification separately, this is under "6.5.16 Assignment Operators". For instance, whether the right side is evaluated first before the += takes place?

    There's this blurb, but my "spec-foo" isn't quite good enough to decipher :)

    Screen Shot 2021-07-16 at 10 26 30 AM

  7. added
    proposalThis issue suggests language modifications. If it also has the "accepted" label then it is planned.
    on Aug 6, 2021
  8. added this to the 0.9.0 milestone on Aug 6, 2021
  9. martinhath commented on Aug 21, 2021

    @martinhath
    Contributor

    I'm fairly sure this is straight up UB in C(++), but just to point out another thing,

    // this behaves like Zig's +=
    bla.val = bla.val + func(&bla);
    // this behaves like C's +=
    bla.val = func(&bla) + bla.val;

    Both of these are the same in C(++), and probably also Zig(?), because there's no guarantee of left-to-right evaluation of subexpressions. I think you meant

    // this behaves like Zig's +=
    var t0 = bla.val;
    var t1 = func(&bla);
    bla.val = t0 + t1
    // this behaves like C's +=
    int t0 = func(&bla);
    int t1 = bla.val;
    bla.val = t0 + t1;

    or something along those lines, which is properly defined and all is well in either language 😄

  10. Validark commented on Nov 14, 2021

    @Validark
    Contributor

    "with respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation"
    I could be wrong but that sounds like:

    // (shortened, assume the address of bla.val wasn't changed by func, see the pic below)
    int t0 = func(&bla);
    bla.val += t0; // is this a "single evaluation"??

    Here's the full context from here
    image

    Another clue is that the atomic version explicitly evaluates E2 before grabbing the value in addr, the saved address of E1. Looks like defined behavior to me, but I've never looked at this spec before so I haven't read the working definitions used here.

  11. modified the milestones: 0.9.0, 0.10.0 on Nov 24, 2021
  12. modified the milestones: 0.10.0, 0.11.0 on Apr 16, 2022
  13. sharpobject commented on Apr 19, 2022

    @sharpobject
    Contributor

    Some people interpret "with respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation" instead to apply to situations like z = foo() + (x += y), where it would mean "the x += y will not be interleaved with the execution of foo(), it will happen entirely before or entirely after foo(), which would not be guaranteed if you wrote z = foo() + (x = x + y)."

    Unfortunately this interpretation doesn't say anything about the topic of this issue.

  14. modified the milestones: 0.11.0, 0.12.0 on Jun 19, 2023
  15. modified the milestones: 0.13.0, 0.12.0 on Jul 9, 2023
  16. modified the milestones: 0.14.0, 0.15.0 on Feb 9, 2025
  17. andrewrk commented on Apr 24, 2026

    @andrewrk
    Member

    Consolidating into #25315

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    proposalThis issue suggests language modifications. If it also has the "accepted" label then it is planned.

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions