[Bug tree-optimization/94356] New: Missed optimisation: useless multiplication generated for pointer comparison
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94356 Bug ID: 94356 Summary: Missed optimisation: useless multiplication generated for pointer comparison Product: gcc Version: 9.3.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: tree-optimization Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- The closest existing ticket I found for this one is https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48316 but this seems different enough, although it might be linked. Consider the function: typedef int t[10]; int f(t *p, long long o) { return p < p+o; } GCC 9.3 with -O2, targeting x86-64, correctly simplifies by p, but still generates a 64x64->64 multiplication in the computation of the offset: f: imulq $40, %rsi, %rsi xorl%eax, %eax testq %rsi, %rsi setg%al ret (Compiler Explorer link: https://gcc.godbolt.org/z/_pT8E- ) Clang 10 avoids generating the multiplication on this example: f: # @f xorl%eax, %eax testq %rsi, %rsi setg%al retq A variant of this example is this other function g with two offsets: int g(t *p, long long o1, long long o2) { return p+o1 < p+o2; } Clang generates the same code as for “o1https://gcc.godbolt.org/z/sDJyHP
[Bug target/94650] New: Missed x86-64 peephole optimization: x >= large power of two
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94650 Bug ID: 94650 Summary: Missed x86-64 peephole optimization: x >= large power of two Product: gcc Version: 9.3.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: target Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- Consider the three functions check, test0 and test1: (Compiler Explorer link: https://gcc.godbolt.org/z/Sh4GpR ) #include #define LARGE_POWER_OF_TWO (1UL << 40) int check(unsigned long m) { return m >= LARGE_POWER_OF_TWO; } void g(int); void test0(unsigned long m) { if (m >= LARGE_POWER_OF_TWO) g(0); } void test1(unsigned long m) { if (m >= LARGE_POWER_OF_TWO) g(m); } At least in the case of check and test0, the optimal way to compare m to 1<<40 is to shift m by 40 and compare the result to 0. This is the code generated for these functions by Clang 10: check: # @check xorl%eax, %eax shrq$40, %rdi setne %al retq test0: # @test0 shrq$40, %rdi je .LBB1_1 xorl%edi, %edi jmp g # TAILCALL .LBB1_1: retq In contrast, GCC 9.3 uses a 64-bit constant that needs to be loaded in a register with movabsq: check: movabsq $1099511627775, %rax cmpq%rax, %rdi seta%al movzbl %al, %eax ret test0: movabsq $1099511627775, %rax cmpq%rax, %rdi ja .L5 ret .L5: xorl%edi, %edi jmp g In the case of the function test1 the comparison is between these two version, because the shift is destructive: Clang10: test1: # @test1 movq%rdi, %rax shrq$40, %rax je .LBB2_1 jmp g # TAILCALL .LBB2_1: retq GCC9.3: test1: movabsq $1099511627775, %rax cmpq%rax, %rdi ja .L8 ret .L8: jmp g It is less obvious which approach is better in the case of the function test1, but generally speaking the shift approach should still be faster. The register-register move can be free on Skylake (in the sense of not needing any execution port), whereas movabsq requires an execution port and also it's a 10-byte instruction!
[Bug middle-end/94651] New: Missed peephole optimization: m >= POWER_OF_TWO || n >= POWER_OF_TWO
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94651 Bug ID: 94651 Summary: Missed peephole optimization: m >= POWER_OF_TWO || n >= POWER_OF_TWO Product: gcc Version: 9.3.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: middle-end Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- Consider the functions: (Compiler Explorer link: https://gcc.godbolt.org/z/Uzd6nd ) #define POWER_OF_TWO (1UL << 20) int check(unsigned long m, unsigned long n) { return m >= POWER_OF_TWO || n >= POWER_OF_TWO; } void g(unsigned long, unsigned long); void test1(unsigned long m, unsigned long n) { if (m >= POWER_OF_TWO || n >= POWER_OF_TWO) g(m, 0); } void test2(unsigned long m, unsigned long n) { if (m >= POWER_OF_TWO || n >= POWER_OF_TWO) g(m, n); } At least for the test1 and test2 functions, it seems that code that implements (m|n) >= POWER_OF_TWO will be faster on average for more input distributions than code with two comparisons on pretty much every modern architecture. This is what Clang 10 generates: check: # @check orq %rsi, %rdi xorl%eax, %eax cmpq$1048575, %rdi # imm = 0xF seta%al retq test1: # @test1 orq %rdi, %rsi cmpq$1048576, %rsi # imm = 0x10 jb .LBB1_1 xorl%esi, %esi jmp g # TAILCALL .LBB1_1: retq test2: # @test2 movq%rsi, %rax orq %rdi, %rax cmpq$1048576, %rax # imm = 0x10 jb .LBB2_1 jmp g # TAILCALL .LBB2_1: retq GCC 9.3 does one comparison after the other. This leads to extra instructions being necessary afterwards for the function check on x86, although it saves one register-register move in the function test2: check: cmpq$1048575, %rdi seta%al cmpq$1048575, %rsi seta%dl orl %edx, %eax movzbl %al, %eax ret test1: cmpq$1048575, %rdi ja .L6 cmpq$1048575, %rsi ja .L6 ret .L6: xorl%esi, %esi jmp g test2: cmpq$1048575, %rdi ja .L10 cmpq$1048575, %rsi ja .L10 ret .L10: jmp g
[Bug c/94658] New: Incorrect transformation with -fstrict-aliasing
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94658 Bug ID: 94658 Summary: Incorrect transformation with -fstrict-aliasing Product: gcc Version: 9.3.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- C17 6.5:6 says: The effective type of an object for an access to its stored value is the declared type of the object, if any.88) If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value. If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one. For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access. I interpret the words “If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value” as saying that the repurposing of dynamically allocated memory is allowed in C. A write of a new value to dynamically allocated memory is not a “subsequent access that does not modify the stored value”, so it simply changes the type of the memory to the type of the lvalue being used for the write. Consider the following program, which was provided by Sorav Bansal: (Compiler Explorer link: https://gcc.godbolt.org/z/Vg25xq ) #include #include #include #include int a; __attribute__((__noinline__)) int foo(float *x, int *y, int *z) { int sum = 1; for (int i = 0; i < a; i++) { *z = 0x123; *x = 3.0f; sum *= *y; *z = 0x40400300; } return sum; } int main() { char *f; f = malloc(32); if (!f) abort(); int i = 3; a = 1; foo((float*)f, &i, (int*)f); float fl; memcpy(&fl, f, 4); printf("f = %f\n", fl); } According to my interpretation above (“repurposing of dynamically allocated memory is allowed”), this program does nothing wrong. The memory location that both x and z point to is repurposed many times, but it is never read inside function foo, and the function main reads it carefully using memcpy. The behavior I expect for this program is to print: f = 3.000183 (3.000183 is the float represented by 0x40400300.) However, when -fstrict-aliasing is enabled, the compiler moves the assignment “*x = 3.0f;” after the loop and makes the compiled program print: f = 3.00 I think the transformation is wrong here. The function foo does not invoke UB when passed z and x aliasing pointers to dynamically allocated memory, and the “naïve memory model” behavior of the function should have been preserved for such a calling context. Yes, this would mean that Clang has independently implemented the same bug, but I am still confident enough that the C standard intended to allow the repurposing of dynamically allocated memory to report this. Please accept my apologies if I am wrong. I have heard it said that the C++ standard has a different philosophy, but this is a bug report against the C compiler.
[Bug c/94658] Incorrect transformation with -fstrict-aliasing
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94658 --- Comment #1 from Pascal Cuoq --- Clang bug for the same optimization in Clang 10 filed at https://bugs.llvm.org/show_bug.cgi?id=45607
[Bug tree-optimization/57359] store motion causes wrong code for union access at -O3
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57359 --- Comment #25 from Pascal Cuoq --- Would it be reasonable to have three settings for -fstrict-aliasing, rather then the current two? - off - strict - assume-no-reuse (I would let you find a better name for the third one.) It seems to me that the wrong transformation corresponds to code patterns that a C developers familiar with the compiled code would be able to tell are used or not in the compiled code: repurposing of dynamically allocated memory and access to a union through pointers. (Note: in https://trust-in-soft.com/wp-content/uploads/2017/01/vmcai.pdf we remarked that GCC is particularly user-friendly in both documenting what uses of unions are compatible with -fstrict-aliasing and in sticking to the documented behavior. The place in the GCC documentation where this is documented would already explain a lot of the context for explaining the difference between the strict and assume-no-reuse settings.) Even if you set -fstrict-aliasing=assume-no-reuse as implied by -O2, this would still be a much welcome improvement compared to the current situation. GCC would continue to be compared fairly in benchmarks to other compilers that have the same approximation, most programs would continue to work fine because they do not rely on the dangerous patterns, and those that rely on the dangerous pattern would have a way out. It would be vaguely comparable to -ffast-math. One possible psychological drawback may be that if the “strict” option exists, some users may ask why GCC does not stick to it in the -On settings.
[Bug c/93031] New: Wish: When the underlying ISA does not force pointer alignment, option to make GCC not assume it
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93031 Bug ID: 93031 Summary: Wish: When the underlying ISA does not force pointer alignment, option to make GCC not assume it Product: gcc Version: 9.2.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- GCC assumes that pointers must be aligned as part of its optimizations, even if the ISA does not force it (for instance, x86-64 without the vector instructions). The present feature wish as for an option to make it not make this assumption. Since the late 1990s, GCC has been adding optimizations based on undefined behavior, and “breaking” existing C programs that used to “work” by relying on the assumption that since they were compiled for architecture X, they would be fine. The reasonable developers have been kept happy by giving them options to preserve the old behavior. These options are -fno-strict-aliasing, -fwrapv, ... and I think there should be another one. In 2016, Pavel Zemtsov showed that a C program that was written assuming that misaligned pointer accesses are allowed could be compiled by GCC to code that did not work as intended: http://pzemtsov.github.io/2016/11/06/bug-story-alignment-on-x86.html This was an interesting development, but GCC's behavior was fair: Pavel had not disabled the vector instructions, GCC had automatically inserted these instructions in the generated code, so the target architecture was not really one that allowed all misaligned pointer accesses. Using -mno-sse would have fixed the behavior. I have recently noticed that since at least version 8.1, GCC assumes that all pointers are aligned even when the target ISA really has no such restriction. This can be seen by compiling the two functions below (Compiler Explorer link: https://gcc.godbolt.org/z/UBBD2Y ) int h(int *p, int *q){ *p = 1; *q = 1; return *p; } typedef __attribute__((__may_alias__)) int I; I k(I *p, I *q){ *p = 1; *q = 1; return *p; } Compiling with GCC 8.1 or 9.2, with either the options “-O2” or “-O2 -fno-strict-aliasing”, GCC generates code that assumes that the functions will always return 1. It does so because it assumes both p and q to be aligned pointers. I would like to have an option in order to make it not make this assumption. (This feature wish is not related to strict aliasing. I have only used the option and the attribute in order to show that the optimization at play here is not related to strict aliasing, and that there is currently no documented way (that I know of) to disable it after having passed “-O2”.) A context in which the functions are called may be: int main(void) { char t[6]; return h((int*)t, (int*)(t+2)); } This context violates strict aliasing, but when using __attribute__((__may_alias__)) or -fno-strict-aliasing, this should not be relevant. The idea here would be to have an option to keep legacy code that used to work working. Modern C code should probably use memcpy to access sequences of bytes that are to be treated as a word but may not be aligned in memory, since both Clang and GCC usually makes these memcpy free. This feature wish is partially related to the bug report about packed structs https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51628 which ended with the addition of a new warning -Waddress-of-packed-member, but it is concerned with architectures on which misaligned accesses are allowed, not with architectures on which they are not.
[Bug c/93031] Wish: When the underlying ISA does not force pointer alignment, option to make GCC not assume it
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93031 --- Comment #3 from Pascal Cuoq --- @amonakov The two blog posts below exist themselves, and describe tools that exist, because software that makes misaligned access exists, although it seems to be a “examples too numerous to list” situation (or, more optimistically, perhaps one where the source code is fixed upstream as problems are found). https://blogs.oracle.com/d/on-misaligned-memory-accesses https://blog.quarkslab.com/unaligned-accesses-in-cc-what-why-and-solutions-to-do-it-properly.html (In the end it's the binary executable that doesn't work, and both posts deal with that aspect at some point, but these executables were not written in SPARC or respectively ARM assembly. If they had been, they would have been written to work. Instead, they were written in a higher-level language that was translated to SPARC/ARM assembly, presumably C.) For a specific example, fifteen minutes of looking around knowing what one is looking for turns up the LZO implementation from http://www.oberhumer.com/opensource/lzo/ . In the latest version to date, 2.10: #if (LZO_ARCH_ALPHA) # define LZO_OPT_AVOID_UINT_INDEX 1 #elif (LZO_ARCH_AMD64) # define LZO_OPT_AVOID_INT_INDEX 1 # define LZO_OPT_AVOID_UINT_INDEX 1 # ifndef LZO_OPT_UNALIGNED16 # define LZO_OPT_UNALIGNED16 1 # endif # ifndef LZO_OPT_UNALIGNED32 # define LZO_OPT_UNALIGNED32 1 # endif # ifndef LZO_OPT_UNALIGNED64 # define LZO_OPT_UNALIGNED64 1 # endif #elif (LZO_ARCH_ARM) # if defined(__ARM_FEATURE_UNALIGNED) # if ((__ARM_FEATURE_UNALIGNED)+0) #ifndef LZO_OPT_UNALIGNED16 #define LZO_OPT_UNALIGNED16 1 #endif #ifndef LZO_OPT_UNALIGNED32 #define LZO_OPT_UNALIGNED32 1 #endif # endif # elif 1 && (LZO_ARCH_ARM_THUMB2) ... #if (LZO_OPT_UNALIGNED32) LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(*(lzo_memops_TU4p)0)==4) #define LZO_MEMOPS_COPY4(dd,ss) \ * (lzo_memops_TU4p) (lzo_memops_TU0p) (dd) = * (const lzo_memops_TU4p) (const lzo_memops_TU0p) (ss) #elif defined(lzo_memops_tcheck__) #define LZO_MEMOPS_COPY4(dd,ss) \ LZO_BLOCK_BEGIN if (lzo_memops_tcheck__(lzo_memops_TU4,4,1)) { \ * (lzo_memops_TU4p) (lzo_memops_TU0p) (dd) = * (const lzo_memops_TU4p) (const lzo_memops_TU0p) (ss); \ } else { LZO_MEMOPS_MOVE4(dd,ss); } LZO_BLOCK_END #else #define LZO_MEMOPS_COPY4(dd,ss) LZO_MEMOPS_MOVE4(dd,ss) #endif ... It is good news that this particular piece of software is already designed to work on compilation platforms where misaligned accesses are forbidden. But if anyone compiles it today with GCC for amd64 or for “__ARM_FEATURE_UNALIGNED”, they are at risk of an optimization firing and making the library not work as intended, perhaps in obscure cases with safety or security implications. I will state, despite the risk of tedious repetition, that the LZO implementation invokes Undefined Behavior. I know it, Oberhumer clearly knows it, and anyone who has read this far knows it. However the perception of some GCC users (not me!) may be that GCC is once again changing the rules and taking an Undefined Behavior that would obviously never cause an actual demon to come out of one's nose, such as a source file missing a final newline, and changing it into one that does.
[Bug c/86026] New: Document and/or change allowed operations on integer representation of pointers
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86026 Bug ID: 86026 Summary: Document and/or change allowed operations on integer representation of pointers Product: gcc Version: 8.1.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- This report is about GCC's handling of low-level pointer tricks such as the XOR linked list [1]. Uses of this sort of trick are present in legacy C code and in newly written code that push the boundaries of what is possible. If users of GCC are going to apply GCC to this sort of code, they need to know what is allowed. This relates to the discussion about pointer provenance, for which Kayvan Memarian and Peter Sewell wrote a discussion draft in 2016 [3]. Consider the three functions below: char f_ptradd(ptrdiff_t o) { g = 1; *(t + o) = 2; return g; } char f_intadd(ptrdiff_t o) { g = 1; *(char*)((uintptr_t)t + o) = 2; return g; } char f_intxor(ptrdiff_t o) { g = 1; *(char*)((uintptr_t)t ^ o) = 2; return g; } GCC 8.1 produces the following assembly for these functions [2]: f_ptradd: movb $1, g(%rip) movl $1, %eax movb $2, t(%rdi) ret f_intadd: movb $1, g(%rip) movl $1, %eax movb $2, t(%rdi) ret f_intxor: xorq $t, %rdi movb $1, g(%rip) movb $2, (%rdi) movzbl g(%rip), %eax ret The third function does exactly what a XOR linked list implementation does. Sufficiently smart inlining and constant propagation would turn a generic implementation into f_intxor. GCC 8.1 and earlier versions compile the first two functions f_ptradd and f_intadd as if they could never be invoked as in the following main: int main(void) { f_ptradd((uintptr_t) &g - (uintptr_t) t); f_intadd((uintptr_t) &g - (uintptr_t) t); f_intxor((uintptr_t) &g ^ (uintptr_t) t); } This is fair in the case of f_ptradd, which invokes undefined behavior at “t + o”. However, I would have expected f_intadd to be compiled conservatively, because it is possible to pass a value o that, added to the integer representation of (char*)t, produces the integer representation of g. Of course, it is for the GCC developers to decide exactly what is allowed and what isn't after a pointer is converted to an integer. But without an explicit explanation in GCC's documentation of what makes f_intadd and f_intxor different, we have to assume that the latter is no more supported than the former, and that programs that use XOR linked lists or similar data structures cannot be compiled by GCC. [1] https://en.wikipedia.org/wiki/XOR_linked_list [2] https://godbolt.org/g/DYCpjS [3] http://www.cl.cam.ac.uk/~pes20/cerberus/n2090.html
[Bug c/86026] Document and/or change allowed operations on integer representation of pointers
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86026 --- Comment #2 from Pascal Cuoq --- Created attachment 44223 --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=44223&action=edit Complete source code for functions in the description
[Bug c/91199] New: In -fexcess-precision=standard mode, the warning “floating constant truncated to zero” is misleading
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91199 Bug ID: 91199 Summary: In -fexcess-precision=standard mode, the warning “floating constant truncated to zero” is misleading Product: gcc Version: 9.1.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- Consider the following program: #include #include #define MY_HUGE_VALF 0x1.0p255f float f; int main(void) { f = 0x1.0p-255f * MY_HUGE_VALF; printf("%d, %f\n", (int)FLT_EVAL_METHOD, f); } When compiled with -O -fexcess-precision=standard -mfpmath=387 and executed, this program prints “2, 1.00”, which indicates that neither factor is 0 and neither is +inf. Compiler Explorer link: https://gcc.godbolt.org/z/RA_tDw This all corresponds exactly to the C standard [1] and Joseph Myers's post [2] introducing the option -fexcess-precision=standard, but please look at the warnings emitted during compilation: :9:3: warning: floating constant truncated to zero [-Woverflow] :9:3: warning: floating constant exceeds range of 'float' [-Woverflow] The second warning, about MY_HUGE_VALF, is not wrong, and probably corresponds to an aspect of the program that should be brought to the attention of the developer. The first warning is wrong: it implies that 0x1.0p-255f will be interpreted as 0 by the compiler, while it (correctly) isn't. I would suggest to make the first warning more like the second one, along the lines of “floating constant underflows range of 'float'”. [1] https://port70.net/~nsz/c/c11/n1570.html#5.2.4.2.2p9 [2] https://gcc.gnu.org/ml/gcc-patches/2008-11/msg00105.html
[Bug c/89734] New: const qualifier on return type not erased inside __typeof__
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89734 Bug ID: 89734 Summary: const qualifier on return type not erased inside __typeof__ Product: gcc Version: 8.2.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- I think that this report is related to the implementation of a resolution from DR 423 in 2017: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=39985#c5 I would expect the following C program to be accepted: #define CONST(x) __typeof__(const __typeof__(x)) #define POINTER(x) __typeof__(__typeof__(x) *) #define ARRAY(x, n) __typeof__(__typeof__(x)[n]) #define FUNCTION(x, y) __typeof__(__typeof__(y)(__typeof__(x))) extern int (* const p(int))[5]; FUNCTION(int, CONST(POINTER(ARRAY(int,5 p; According to Compiler Explorer, it is accepted by Clang, and by GCC version up to 6.3, but not by GCC version 7 and above, which complains: error: conflicting types for 'p' Compiler Explorer link: https://gcc.godbolt.org/z/c_9JTu This may be related to how function types are handled inside __typeof__. If the const attribute is not erased inside __typeof__, then it would not match the type build for a plain declaration, where the const attribute is erased since revision 236231. If that were the case, then a solution would be to make the __typeof__ extension more uniform with the rest of the language.
[Bug c/89872] New: GCC does not generate read access to volatile compound literal
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89872 Bug ID: 89872 Summary: GCC does not generate read access to volatile compound literal Product: gcc Version: 8.3.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- This report is similar to but different from my previous report https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82340 Consider the C code below. The report is about the compilation of the functions g and hg. The other functions are included only for discussion of the expected behavior. void f(void) { volatile int y=1, z=2; y + z; } void g(void) { (volatile int){1} + (volatile int){2}; } void k(void) { (volatile int){1}; } void hf(void) { for (int i = 0; i < 1000; i++) f(); } void hg(void) { for (int i = 0; i < 1000; i++) g(); } void hk(void) { for (int i = 0; i < 1000; i++) k(); } When compiling with -O3, the versions trunk and 8.3 of GCC on Compiler Explorer (https://gcc.godbolt.org/z/2Il4GG ) produce the following x86-64: f: movl$1, -8(%rsp) movl$2, -4(%rsp) movl-8(%rsp), %eax movl-4(%rsp), %eax ret g: ret k: movl$1, -4(%rsp) movl-4(%rsp), %eax ret hf: movl$1000, %eax .L6: movl$1, -8(%rsp) movl$2, -4(%rsp) movl-8(%rsp), %edx movl-4(%rsp), %edx subl$1, %eax jne .L6 ret hg: ret hk: movl$1000, %eax .L10: movl$1, -4(%rsp) movl-4(%rsp), %edx subl$1, %eax jne .L10 ret The functions g and hg are compiled to “ret”. Because reading from a volatile lvalue is an observable side-effect (C11 5.1.2.3:6 https://port70.net/~nsz/c/c11/n1570.html#5.1.2.3p6 ) I would have expected them to be compiled more similarly to f and hf respectively.
[Bug c/89888] New: When switch controlling expression is promoted from type narrower than int, GCC does not diagnose identical cases
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89888 Bug ID: 89888 Summary: When switch controlling expression is promoted from type narrower than int, GCC does not diagnose identical cases Product: gcc Version: 8.3.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- Consider the C function: long long X; void f(unsigned char x) { switch(x) { case -1: X=-1; break; case 0x: X=0x; break; } } The controlling expression of the switch, x, has type unsigned char and is promoted to int before its type being used as reference for the constants -1 and 0x. This is according to C11 6.8.4.2:5 (https://port70.net/~nsz/c/c11/n1570.html#6.8.4.2p5 ) GCC 8.3 emits very good warnings about each of the constants being, after conversion, outside the range of an unsigned int and thus unreachable: : In function 'f': :6:5: warning: case label value is less than minimum value for type case -1: X=-1; break; ^~~~ :7:5: warning: case label value is less than minimum value for type case 0x: X=0x; break; ^~~~ (Compiler Explorer link: https://gcc.godbolt.org/z/gvnvoa ) However, GCC does not warn about the labels being identical after conversion. I feel silly reporting this, because it only happens for discarded labels that were unreachable, and there isn't any ambiguity about the meaning of the program. Still, the C11 clause 6.8.4.2:3 about identical switch case labels (after conversion) (https://port70.net/~nsz/c/c11/n1570.html#6.8.4.2p3 ) is under a “Constraints” heading, so depending how much GCC cares about adhering to the letter of the standard, it may want to diagnose this situation. Clang diagnoses this situation and emits an “error”: :7:10: error: duplicate case value '-1' Clang also emits two misleading warnings about the constants -1 and 0x. The wording of these warnings is so misleading that it can be considered a Clang bug, which has been reported in the appropriate place.
[Bug c/89888] When switch controlling expression is promoted from type narrower than int, GCC does not diagnose identical cases
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89888 --- Comment #1 from Pascal Cuoq --- errata: “outside the range of an unsigned char”
[Bug c/90472] New: “extern int i;” declaration inside function is allowed to shadow “static int i;” at file scope
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90472 Bug ID: 90472 Summary: “extern int i;” declaration inside function is allowed to shadow “static int i;” at file scope Product: gcc Version: 9.1.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- Consider the two compilation units below, that differ only in the name of the automatic variable inside function f: int *p1, *p2; static int i = 1; void f(void) { p1 = &i; int i = 2; { extern int i; p2 = &i; } } int *p1, *p2; static int i = 1; void f(void) { p1 = &i; int j = 2; { extern int i; p2 = &i; } } Using GCC 9.1, the first file is accepted and the “obvious” assembly code is generated (the function f assigns the same value to p1 and p2). The second file is rejected with the error message: : In function 'f': :9:20: error: variable previously declared 'static' redeclared 'extern' 9 | extern int i; |^ Compiler Explorer link: https://gcc.godbolt.org/z/wrvZ1d I rather agree with the error message, and my understanding of C11 6.7:3 (https://port70.net/~nsz/c/c11/n1570.html#6.7p3 ) is that both compilation units should be rejected. In any case, the two look equivalent, and they should probably either be both accepted or both rejected. (If you arrive to the conclusion that the C11 standard says they should both be rejected, I will have a similar bug to report in Clang.)
[Bug c/90472] “extern int i;” declaration inside function is allowed to shadow “static int i;” at file scope
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90472 --- Comment #2 from Pascal Cuoq --- Thanks for this link. So the bug report is that the file below is rejected by GCC 9.1 (and every GCC version present on Compiler Explorer down to 4.1.2), whereas according to the arguments in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=14366 it should be accepted. int *p1, *p2; static int i = 1; void f(void) { p1 = &i; int i = 2; { extern int i; p2 = &i; } }
[Bug c/84764] New: Wrong warning "so large that it is unsigned" for __int128 constant
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84764 Bug ID: 84764 Summary: Wrong warning "so large that it is unsigned" for __int128 constant Product: gcc Version: 7.3.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- According to the output of the following program, the type of the constant 1000 is __int128, which is a signed type: Compiler Explorer link: https://godbolt.org/g/kFTNaz #include #define prtype(X) \ _Generic((X), \ int: "int",\ long: "long", \ long long: "long long",\ unsigned int: "unsigned int", \ unsigned long: "unsigned long",\ unsigned long long: "unsigned long long", \ __int128: "__int128", \ default: "?") int main(void) { printf("1 %s\n", prtype(1)); printf("30 %s\n", prtype(30)); printf("1000 %s\n", prtype(1000)); } ___ Output: 1 int 30 long 1000 __int128 However a warning is emitted on the last line for the constant 1000: prtype.c:17:66: warning: integer constant is so large that it is unsigned This warning implies that the constant is typed as long long, which has been the case either historically or with other -std= settings. However, for the compilation settings at hand, the warning is wrong. It should at most say that the constant is typed with a type outside of the "int, long, long long" hierarchy of standard types. For reference the relevant clause of the C11 standard is 6.4.4: https://port70.net/~nsz/c/c11/n1570.html#6.4.4
[Bug c/84764] Wrong warning "so large that it is unsigned" for __int128 constant
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84764 --- Comment #1 from Pascal Cuoq --- I meant "the warning implies that the constant is typed as unsigned long long...".
[Bug c/82340] New: volatile ignored in compound literal
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82340 Bug ID: 82340 Summary: volatile ignored in compound literal Product: gcc Version: 8.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- Consider the function f below: int f(void) { volatile char *p = (volatile char[1]){1}; for (int i=1; i<10; i++) *p=4; return *p; } Volatile access is a visible side-effect, so one may expect the generated code for the function f to do “something” nine times, for some definition of “something”. In GCC 7.2 and in gcc.godbolt.org's current snapshot of “gcc (trunk)”, the function f is compiled to: f: movl$4, %eax ret Command: gcc -O3 -std=c11 -xc -pedantic -S t.c Link: https://godbolt.org/g/4Ua1Ud I would expect function f to be compiled to something that ressembles the code produced for function g, or the code produced by Clang for f: int g(void) { volatile char t[1] = {1}; volatile char *p = t; for (int i=1; i<10; i++) *p=4; return *p; } g: movb$1, -1(%rsp) movb$4, -1(%rsp) movb$4, -1(%rsp) movb$4, -1(%rsp) movb$4, -1(%rsp) movb$4, -1(%rsp) movb$4, -1(%rsp) movb$4, -1(%rsp) movb$4, -1(%rsp) movb$4, -1(%rsp) movsbl -1(%rsp), %eax ret
[Bug c/82340] volatile ignored in compound literal
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82340 --- Comment #2 from Pascal Cuoq --- Richard: > I don't see how a volatile compound literal could make any sense or how > you'd observe the side-effect of multiple stores to it Well, I have the same question about volatile variables the address of which is not taken. But this is off-topic for this bug report, in which the volatile's object's address is taken. > (IIRC compound literals are constant!?). The C11 standard invites the programmer to use the const qualifier if they want a constant compound literal, and gives an explicit example of a “modifiable” non-const compound literal: https://port70.net/~nsz/c/c11/n1570.html#6.5.2.5p12
[Bug c/80409] New: Document that va_arg(ap, void*) can be used to consume any pointer argument
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80409 Bug ID: 80409 Summary: Document that va_arg(ap, void*) can be used to consume any pointer argument Product: gcc Version: unknown Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- This bug report is vaguely related to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=26542 but whereas that report asks for removing a warning, here there is no warning and this is a request for it to be documented that the idiom is legal in GCC. The C11 standard allows to confuse void* and char* when consuming arguments from a va_list, and only these types of pointers: http://port70.net/~nsz/c/c11/n1570.html#7.16.1.1p2 As explained by Rich Felker as part of a discussion on musl's scanf implementation, this makes it very inconvenient to implement POSIX's positional specifiers for scanf. Clearly the intent of POSIX's “all the leading arguments, from the first to the (N-1)th, are pointers” is to allow these arguments to be consumed on the spot when %N$ is encountered: http://www.openwall.com/lists/musl/2017/04/10/3 Since the open-source world divides the C compilation platform described by the C standard into C compilers (Clang, GCC) and standard libraries (Glibc, musl, ...), with the implementation of varargs on the compiler side and the implementation of scanf on the standard library side, it would make sense to document that on target platforms where the representation of pointers is uniform, the compilers allow va_arg(ap, void*) to consume any pointer argument.
[Bug c/80410] New: Improve merging of identical branches
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80410 Bug ID: 80410 Summary: Improve merging of identical branches Product: gcc Version: 7.0.1 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- Consider the following code: va_list ap; void *result; va_start(ap, ptr_type); switch (ptr_type) { case 0: result = va_arg(ap, int*); break; case 1: result = va_arg(ap, char*); break; case 2: result = va_arg(ap, short*); break; default: result = va_arg(ap, void*); break; } va_end(ap); GCC correctly recognizes that the assembly code for the cases 0, 1, 2 is identical, which is the difficult part (the source code for the branches is different and cannot be merged by the programmer unless https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80409 is resolved). At the time of writing, the Compiler Explorer trunk version produces: movq%rax, -56(%rsp) je .L15 cmpl$2, %edi je .L15 testl %edi, %edi je .L16 .L15: movq-56(%rsp), %rax .L16: (Compiler Explorer link: https://godbolt.org/g/0NvnSX ) It would be nice if GCC was able to omit all the conditional branches, since they all lead to doing the same thing at the assembly level.
[Bug c/80409] Document that va_arg(ap, void*) can be used to consume any pointer argument
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80409 --- Comment #2 from Pascal Cuoq --- > it should work even with standard c Quoting from 7.6.1.1:2 … the behavior is undefined, except for the following cases: * … * one type is pointer to void and the other is a pointer to a character type. Why would the standard say the above, if using void* with any pointer type worked? > for an example %p in printf takes a void* but nobody casts it to void* when > passing to printf This does not correspond to my experience. The C programmers I know fall into two categories: - C programmers who do not cast printf %p arguments to void*, because it works. These programmers also write “*(float*)&uint_variable = 1.0f;”, because it works, and “if (int_variable + 1 < int_variable) printf ("overflow!");”, because it works. - C programmers who do cast printf %p arguments to void*. The good ship “we shouldn't have to document this because it obviously should work on normal architectures even if the C standard does not say so” sailed some time in the late nineties.
[Bug c/96788] "integer constant is so large that it is unsigned" warning is incorrect
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96788 Pascal Cuoq changed: What|Removed |Added CC||pascal_cuoq at hotmail dot com --- Comment #6 from Pascal Cuoq --- Joseph Myers wrote: > The warnings are attempting to cover both the C90 > case where a decimal constant too large for signed long can be unsigned > long, and the case of a constant too large for intmax_t. In the case of the constant 1000, each of the two kind of warnings, “ integer constant is so large that it is unsigned” and “this decimal constant is unsigned only in ISO C90” can be misleading. Consider the compilation unit: int x; void f(void) { x = -1000 < 0; } 1/ Misleading warning “this decimal constant is unsigned only in ISO C90” Compiler Explorer link: https://gcc.godbolt.org/z/4Eze1f Using GCC 10.2 targeting x86, the compilation options “-O -m32 -std=c89” make GCC compile f as below, and emit the warning “this decimal constant is unsigned only in ISO C90” f: movl$0, x ret Actually, changing the C dialect to C99 does not make the constant not unsigned, since GCC emits the same assembly code for f (setting x to 0) with “-O -m32 -std=c99” 2/ Misleading warning “integer constant is so large that it is unsigned” Compiler Explorer link: https://gcc.godbolt.org/z/MGjn5G Using GCC 10.2 targeting x86, the compilation options “-O -m64 -std=c99” make GCC compile f as below, and emit the warning “integer constant is so large that it is unsigned”. f: movl$1, x(%rip) ret The constant is not unsigned. If it were, the function f would not set x to 1.
[Bug c/97986] New: ICE in force_constant_size when applying va_arg to VLA type
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97986 Bug ID: 97986 Summary: ICE in force_constant_size when applying va_arg to VLA type Product: gcc Version: 11.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- This bug seems most similar to (but still different from) https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59711 (in which a comment says that it appears to have been fixed in GCC 6). In GCC 10.2 and some earlier versions, compiling the following program (Compiler Explorer link: https://gcc.godbolt.org/z/11ob5j ): #include int sum(int n, ...) { va_list ap; va_start(ap, n); int *input = va_arg(ap, int[n]); int rc = 0; for (int i = 0; i < n; i++) rc += input[i]; return rc; } Produces the following error message: In file included from :1: : In function 'sum': :7:29: internal compiler error: in force_constant_size, at gimplify.c:733 7 | int *input = va_arg(ap, int[n]); | ^~~ Please submit a full bug report, with preprocessed source if appropriate. See <https://gcc.gnu.org/bugs/> for instructions. Compiler returned: 1
[Bug c/104607] New: Struct padding not initialized when all members are provided initializers
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104607 Bug ID: 104607 Summary: Struct padding not initialized when all members are provided initializers Product: gcc Version: 12.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- In the GCC versions available as 11.2 and as “trunk” (as of this writing) on Compiler Explorer, the following C code is translated to the following X86-64 assembly, with options “-O2 -std=c17” CE link: https://gcc.godbolt.org/z/4oeh6d7vY void g(void*); int f(void) { struct s { char c; long long m64;} s12 = {1,2}, s1 = {1}; g(&s1); g(&s12); } f: subq$40, %rsp pxor%xmm0, %xmm0 leaq16(%rsp), %rdi movaps %xmm0, 16(%rsp) movb$1, (%rsp) movq$2, 8(%rsp) movb$1, 16(%rsp) callg movq%rsp, %rdi callg addq$40, %rsp ret Please note that GCC initializes all of s1 with “movaps %xmm0, 16(%rsp)” and that initializing only s1.m64 to 0 in addition to initializing s1.c to 1 would have resulted in fewer, shorter instructions. However, s12 is not initialized this way, and indeed the padding of s12 is not initialized at all. The requirement to initialize padding is a change that was introduced between C99 and C11. The relevant clauses are in C99: https://port70.net/~nsz/c/c99/n1256.html#6.7.8p10 If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static storage duration is not initialized explicitly, then: if it has pointer type, it is initialized to a null pointer; if it has arithmetic type, it is initialized to (positive or unsigned) zero; if it is an aggregate, every member is initialized (recursively) according to these rules; if it is a union, the first named member is initialized (recursively) according to these rules. https://port70.net/~nsz/c/c99/n1256.html#6.7.8p19 The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding any previously listed initializer for the same subobject;132) all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration. In C11 the clause that was numbered 6.7.8:10 in C99 becomes 6.7.9:10 and the text is changed as follows: If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static or thread storage duration is not initialized explicitly, then: if it has pointer type, it is initialized to a null pointer; if it has arithmetic type, it is initialized to (positive or unsigned) zero; if it is an aggregate, every member is initialized (recursively) according to these rules, and any padding is initialized to zero bits; if it is a union, the first named member is initialized (recursively) according to these rules, and any padding is initialized to zero bits; Now, I agree that it's possible to read the change in a way that only forces the initialization of partially initialized structs. But can this have been the intention of the standardization committee when they went to the trouble of making an explicit change to C11? The concern was likely to address some security issues with information leaks. If structs with initializers for every members do not have their padding initialized, then security issues with information leaks remain for structs with initializers for every members. Why would the committee have fixed only half of the problem?
[Bug c/104607] Struct padding not initialized when all members are provided initializers
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104607 --- Comment #2 from Pascal Cuoq --- Well that's a quick resolution to a bug report that contained that actual phrase “and any padding is initialized to zero bits”, and this in a quote from the C11 standard, but I guess one of us can't read then.
[Bug c/109956] New: GCC reserves 9 bytes for struct s { int a; char b; char t[]; } x = {1, 2, 3};
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109956 Bug ID: 109956 Summary: GCC reserves 9 bytes for struct s { int a; char b; char t[]; } x = {1, 2, 3}; Product: gcc Version: 14.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- Static-lifetime variables of type “struct with FAM” (flexible array member) with an initializer for the FAM are a GCC extension. As of GCC 13.1 and Compiler Explorer “trunk”, targeting x86, the definition “struct s { int a; char b; char t[]; } x = {1, 2, 3};” reserves 9 bytes for x, and in fact, with various initializers, the trailing padding for variables of type “struct s” is always 3, as if the size to reserve for the variable was computed as “sizeof (struct s) + n * sizeof(element)”. Input file: struct s { int a; char b; char t[]; } x = {1, 2, 3}; Command: gcc -S fam_init.c Result (with Ubuntu 9.4.0-1ubuntu1~20.04.1 which exhibits the same behavior as the recent versions on Compiler Explorer): .align 8 .type x, @object .size x, 9 x: .long 1 .byte 2 .byte 3 .zero 3 Clang up to version 14 used to round up the size of the variable to a multiple of the alignment of the struct, but even this is not necessary. It is only necessary that the size reserved for a variable of type t is at least “sizeof(t)” bytes, and also to reserve enough space for the initializer. Clang 15 and later uses the optimal formula: max(sizeof (struct s), offsetof(struct s, t[n])) Compiler Explorer link: https://gcc.godbolt.org/z/5W7h4KWT1 This ticket is to suggest that GCC uses the same optimal formula as Clang 15 and later.
[Bug c/109956] GCC reserves 9 bytes for struct s { int a; char b; char t[]; } x = {1, 2, 3};
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109956 --- Comment #3 from Pascal Cuoq --- @Andrew Pinski You don't even need to invoke the fact that this is an extension. GCC could reserve 17 bytes for each variable i of type “int”, and as long as “sizeof i” continued to evaluate to 4 (4 being the value of “sizeof(int)” for x86), no-one would be able to claim that GCC is not generating “correct” assembly code. This ticket is pointing out that the current behavior for initialized FAMs is suboptimal for programs that rely on the GCC extension, just like it would be suboptimal to reserve 17 bytes for each “int” variable for standard C programs (and I would open a ticket for it if I noticed such a behavior). It's not breaking anything and it may be inconvenient to change, and as a ticket that does not affect correctness, it can be ignored indefinitely. It's just a suggestion for smaller binaries that might also end up marginally faster as a result. @Martin Uecker Considering how casually phrased the description of FAMs was in C99 and remained in later standards (see https://stackoverflow.com/q/73497572/139746 for me trying to make sense of some of the relevant words), I doubt that the standard has anything to say about the compiler extension being discussed. But if you have convincing arguments, you could spend a few minutes filing a bug against Clang to tell them that they are making the binaries they generate too small and efficient.
[Bug c/109956] GCC reserves 9 bytes for struct s { int a; char b; char t[]; } x = {1, 2, 3};
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109956 --- Comment #13 from Pascal Cuoq --- @Martin I completely agree with comment 12, however about the last paragraph, I would like to point out that for purposes of memcpy'ing to or from such a struct with initialized FAM, it is enough to recommend that programmers use the simple formula “offsetof(struct foo, t) + n * sizeof(char)” (or “offsetof(struct foo, t[n])”. The part that is not copied is the part that they did not intend to use when they chose the initializer of the FAM, and that they cannot portably use because of the padding that may or may not exist for a different target architecture. So since: First, GCC currently does not always reserve enough room to allow “memcpy(…, …, sizeof(struct foo) + n * sizeof(char))”, and second, using the time-proven formula as argument of malloc technically does not always allocate enough space to make it valid to access p->t[n-1] according to the strict interpretation of the words “it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed”, we might as well start recommending that C programmers use “offsetof(struct foo, t) + n * sizeof(char)” as argument of memcpy, and either clarify the meaning of the words “it behaves as if…” in the C standard or prepare for a very unpleasant discussion when we have to tell them the formula they have to use as argument of malloc.
[Bug c/96788] "integer constant is so large that it is unsigned" warning is incorrect
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96788 --- Comment #11 from Pascal Cuoq --- @newbie-02 Sir, this is the GCC bugzilla. You have to contact your national standardization institute so that they convey your suggestions to ISO. Incidentally, while it is subjective whether the individuals making up the standardization committee are highly qualified, I can assure you that they are not paid a cent to participate in the standardization of C.
[Bug c/120710] New: C23 enum member does not have during processing the type indicated by C23 6.7.3.4:12
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120710 Bug ID: 120710 Summary: C23 enum member does not have during processing the type indicated by C23 6.7.3.4:12 Product: gcc Version: 15.1.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: pascal_cuoq at hotmail dot com Target Milestone: --- Testcase: #include enum e { A = 0, B = 0x8000, C = 2 }; #define ty(e) _Generic(e, int:"int", unsigned:"unsigned", long:"long", unsigned long:"unsigned long") enum e1 { G = 0, H = -0x8001L, I, J = _Generic(I, int:147, long:1046) }; int main(void) { printf ("A B C %s %s %s\n\n", ty(A), ty(B), ty(C)); printf ("G H I J %s %s %s %s\n", ty(G), ty(H), ty(I), ty(J)); printf("I %d", (int)J); } Compiled with GCC 15.1 for x86-64, the program outputs: A B C unsigned unsigned unsigned G H I J long long long long I 147 Compiler Explorer link: https://gcc.godbolt.org/z/1dY7n7Kco The surprising behavior is in the last output line that shows that I had type int during the processing of enum e1. https://cigix.me/c23#6.7.3.4 says: During the processing of each enumeration constant in the enumerator list, the type of the enumeration constant shall be: … - the type of the value from the previous enumeration constant with one added to it. If such an integer constant expression would overflow or wraparound the value of the previous enumeration constant from the addition of one, [this is not the case in the example above]
[Bug c/120710] C23 enum member does not have during processing the type indicated by C23 6.7.3.3:12
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120710 Pascal Cuoq changed: What|Removed |Added Summary|C23 enum member does not|C23 enum member does not |have during processing the |have during processing the |type indicated by C23 |type indicated by C23 |6.7.3.4:12 |6.7.3.3:12 --- Comment #1 from Pascal Cuoq --- Sorry, the link to the relevant clause is https://cigix.me/c23#6.7.3.3.p12