Skip to content

c(msvc): #1745 drops MS anonymous tagged/typedef members on *-windows-msvc (Buster predefines _MSC_EXTENSIONS) — sizeof/offsetof and member access now diverge from cl/clang-cl, and the new fixture pins the GNU answer on the Windows lanes #1750

Description

@davidgmbb

Summary

#1745 (fixing #1706) changed c_type_parse_aggregate_segment_step so that a member declaration with no declarators adds a member only if the aggregate is untagged and defined in place. This is correct for GNU targets. But Buster applies it to every target, including *-windows-msvc. On those targets Buster predefines _MSC_VER 1940 and _MSC_EXTENSIONS 1 (c_source.c). The Microsoft C dialect accepts struct S;, union U;, a typedef name, or a nested tagged definition as an anonymous member that adds storage and promotes its fields. MSVC documents this as the "anonymous structures" extension. Clang enables it by default for *-pc-windows-msvc: -fms-extensions is implied, and it emits -Wmicrosoft-anon-tag.

Before #1745, Buster matched the MSVC layout everywhere; that was wrong on GNU targets, which is #1706. After #1745, it matches the GNU layout everywhere, which is wrong on MSVC. Neither side keys the rule on the target. The fixture #1745 added (c_test_tagged_member_declares_nothing) runs on target_native, so on the Windows CI lanes it asserts the non-MSVC sizes. The test and the compiler agree on an answer that the platform's own compiler rejects. That is the same common-mode pattern as #1439.

Evidence

Revisions:

Oracle: clang 18. Both sides are read from the .data image of unsigned long long R[].

Fixture shapes from #1745

The struct Z { struct Fwd; ... } line is omitted. Clang in MS mode rejects it with "field has incomplete type".

struct S { int a; }; union U { int a; double d; }; typedef struct { int z; } A;
struct T { struct S; int b; };
struct V { union U; int b; };
struct W { A; int b; };
struct Y { struct N { int n; }; int b; };
struct X { struct { int p; }; const union { int q; char r[8]; }; int b; };
unsigned long long R[] = { sizeof(struct T), __builtin_offsetof(struct T, b), sizeof(struct V), __builtin_offsetof(struct V, b),
  sizeof(struct W), __builtin_offsetof(struct W, b), sizeof(struct Y), sizeof(struct N), 0, sizeof(struct X), __builtin_offsetof(struct X, b) };
Compiler / target T size,b V size,b W size,b Y, N X size,b
clang x86_64-pc-windows-msvc 8,4 16,8 8,4 8,4 16,12
clang aarch64-pc-windows-msvc 8,4 16,8 8,4 8,4 16,12
clang x86_64-pc-windows-msvc -fno-ms-extensions 4,0 4,0 4,0 4,4 16,12
clang x86_64-w64-windows-gnu 4,0 4,0 4,0 4,4 16,12
clang x86_64-unknown-linux-gnu 4,0 4,0 4,0 4,4 16,12
buster pre-#1745, x86_64-windows-msvc 8,4 16,8 8,4 8,4 16,12 ✅
buster pre-#1745, x86_64-unknown-linux-gnu 8,4 16,8 8,4 8,4 16,12 ❌ (#1706)
buster main f0430c6, x86_64-windows-msvc / aarch64-windows-msvc 4,0 4,0 4,0 4,4 16,12 ❌
buster main f0430c6, x86_64-unknown-linux-gnu 4,0 4,0 4,0 4,4 16,12 ✅

Microsoft's documented example no longer compiles on the MSVC target

This is the example from the MSVC "Anonymous structures" documentation:

struct phone { int areacode; long number; };
struct person { char name[30]; char gender; int age; int weight; struct phone; };
int area(struct person* p) { return p->areacode; }

Bit-field interaction

For the same pattern under the c_record_layout_rule policy of #1452, here are size, align and offset of c, from clang x86_64-pc-windows-msvc:

  • struct d1 { char a; struct T; int b : 4; char c; } gives 16,4,12.
  • struct __attribute__((packed)) d4 { char a; struct T; int b : 20; char c; } gives 10,1,9.
  • union d5 { char a; struct T; int b : 3; } gives 4,4,0.

Pre-#1745 Buster matched these. Main now gives 12,4,8 / 6,1,5 / 4,1,0, which is the -fno-ms-extensions answer.

Root cause

The anonymous-member decision in c_type_parse_aggregate_segment_step (src/buster/lib/compiler/frontend/c/c_parse.c) depends only on the syntax. Whether a tagged or typedef-named aggregate is an anonymous member is a property of the dialect. Buster selects that dialect by target: Windows targets already get _MSC_VER, _MSC_EXTENSIONS, __int64 and so on. Only one oracle target was checked, gcc and clang on x86-64 Linux. The fixture then inherited that target's answer on every lane.

Proposed fix

Related: #1706, #1745, #1439, #1492, PR #1452.

Activity

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions