[CRITICAL] `clang -E` may be inconsitent with `clang -o` regarding #pragma and _Pragma
- 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
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