llvm / llvm/llvm-project

[CRITICAL] `clang -E` may be inconsitent with `clang -o` regarding #pragma and _Pragma

Open
#214,482 0 comments 0 reactions 0 assignees View on GitHub
clang
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

As the title suggests, in some cases `clang -o ` and `clang -E | clang -x -o ` may have different behavior regarding #pragma and _Pragma usage.

Given the following code:
```c
// test.c
#define pack 42
// #define push 43
// #define pop 44

#define PACK_1 1
#define PACK_8 8

int printf(const char *__format, ...);
#define PRINT(i) printf("sizeof(S%d) = %zu, alignof(S%d) = %zu\n", i, sizeof(struct S##i), i, alignof(struct S##i))

#pragma pack(push, PACK_1)
struct S1 { char c; short s; int i; double d; };
#pragma pack(pop)

#pragma pack(push, PACK_8)
struct S2 { char c; short s; int i; double d; };
#pragma pack(pop)

#pragma push_macro("pack")
#pragma push_macro("push")
#pragma push_macro("pop")
#undef pack
#undef push
#undef pop

#define MY_PRAGMA_IMPL(x) _Pragma(#x)
#define MY_PRAGMA_PACK_PUSH(x) MY_PRAGMA_IMPL(pack(push, x))
#define MY_PRAGMA_PACK_POP MY_PRAGMA_IMPL(pack(pop))

#define _PACK_1 MY_PRAGMA_PACK_PUSH(PACK_1)
#define _PACK_8 MY_PRAGMA_PACK_PUSH(PACK_8)
#define _POP MY_PRAGMA_PACK_POP

#pragma pop_macro("pack")
#pragma pop_macro("push")
#pragma pop_macro("pop")

_PACK_1
struct S3 { char c; short s; int i; double d; };
_POP
_PACK_8
struct S4 { char c; short s; int i; double d; };
_POP

int main() { PRINT(1); PRINT(2); PRINT(3); PRINT(4); return 0; }
```

Using clang 22.1.8. `-std=c23` below is just for `alignof`, as I want to test the same code in both C and C++ mode.

```bash
$ clang -std=c23 test.c && ./a.exe
sizeof(S1) = 15, alignof(S1) = 1
sizeof(S2) = 16, alignof(S2) = 8
sizeof(S3) = 15, alignof(S3) = 1
sizeof(S4) = 16, alignof(S4) = 8

$ clang -std=c23 -E test.c | clang -xc -std=c23 - && ./a.exe
sizeof(S1) = 16, alignof(S1) = 8
sizeof(S2) = 16, alignof(S2) = 8
sizeof(S3) = 15, alignof(S3) = 1
sizeof(S4) = 16, alignof(S4) = 8
```

Also tested with clang 21.1.8 on Linux (changing a.exe to a.out), the result is identical.

The result is even weirder if you uncomment `#define push 43` and `#define pop 44`
`clang -std=c23 test.c` now generates tons of warnings like:
```bash
test.c:12:18: warning: missing ')' after '#pragma pack' - ignoring [-Wignored-pragmas]
12 | #pragma pack(push, PACK_1)
| ^
test.c:14:9: warning: expected #pragma pack parameter to be '1', '2', '4', '8', or '16' [-Wignored-pragmas]
14 | #pragma pack(pop)
```
And the result will be:
```
sizeof(S1) = 16, alignof(S1) = 8
sizeof(S2) = 16, alignof(S2) = 8
sizeof(S3) = 16, alignof(S3) = 8
sizeof(S4) = 16, alignof(S4) = 8
```
While `clang -std=c23 -E test.c | clang -xc -std=c23 - && ./a.exe` get (without any warning):
```
sizeof(S1) = 16, alignof(S1) = 8
sizeof(S2) = 16, alignof(S2) = 8
sizeof(S3) = 15, alignof(S3) = 1
sizeof(S4) = 16, alignof(S4) = 8
```

See also: https://sourceforge.net/p/mingw-w64/mailman/message/59370038/

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the two commands with the provided test.c, comparing direct compilation with the preprocessed pipeline. Trace how #pragma pack and _Pragma are handled, including macro expansion and push_macro/pop_macro behavior. Done means both compilation paths produce consistent structure sizes and alignments, with the relevant cases covered by a regression test.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.