[HN Gopher] Detecting if an expression is constant in C
       ___________________________________________________________________
        
       Detecting if an expression is constant in C
        
       Author : ingve
       Score  : 48 points
       Date   : 2025-04-22 09:27 UTC (13 hours ago)
        
 (HTM) web link (nrk.neocities.org)
 (TXT) w3m dump (nrk.neocities.org)
        
       | Y_Y wrote:
       | If C23 is why not use constexpr?
       | 
       | https://en.cppreference.com/w/c/language/constexpr
        
         | lpribis wrote:
         | This is not for declaring constexpr variables, it is about how
         | to implement a checker function that verifies an inline
         | expression is constant. Plus some of the examples work back to
         | C99 instead of C23, which I would wager close to zero people
         | are using in real-world code bases.
        
           | cmptrnerd6 wrote:
           | You'd probably still win the wager but I do want to say there
           | are some of us using C23. We even use it in our embedded
           | systems running on arm based microcontrollers. Though we
           | still do maintain some C89 code :(
        
             | pantalaimon wrote:
             | C23 on embedded ARM is entirely painless as it's all
             | upstream GCC these days anyway.
             | 
             | The problem is if you want to also still support esp8266
             | which is forever stuck at GCC 8.4
        
           | Y_Y wrote:
           | #define C(x) ((constexpr typeof(x)){ (x) })
        
         | Aardwolf wrote:
         | The article mentions C23, but what they're trying to do is
         | detect if something (possibly declared by someone else?) is a
         | compile time constant, not to declare it as such
        
       | re wrote:
       | Is there a use case for such a macro, or is it just a puzzle for
       | its own sake?
        
         | uecker wrote:
         | Probably not. The Linux kernel has one.
        
         | apple1417 wrote:
         | I've seen something related, which returned a bool instead of
         | failing compilation, be used to switch between a path the
         | optimiser could inline and some assembly. You could probably
         | use this to make sure it was always inlined.
        
         | kevingadd wrote:
         | If you're writing code that needs to behave deterministically
         | and not have side effects, you could use this to make
         | violations of determinism/side-effect-freeness fail fast, I
         | guess?
        
         | variadix wrote:
         | The use case that comes to mind is doing manual compile time
         | optimization based on macro arguments. E.g. you have some
         | assembly block that is fast but requires some immediate
         | arguments, and you have a fallback path for the dynamic case,
         | and you want to determine which one to call at compile time
         | based on whether the arguments are constants or not.
        
         | JacksonAllan wrote:
         | I use something similar in a container library to warn the user
         | if he or she supplies an argument with potential side effects
         | to a macro that evaluates it multiple times:
         | 
         | https://github.com/JacksonAllan/CC/blob/42a7d810274a698dff87...
         | 
         | Specifically, if (arg)==(arg) is not a constant expression,
         | then it could have side effects.
         | 
         | However, this mechanism does generate some annoying false
         | positives, as shown below:                 // Create a map with
         | int keys and values that are vectors of floats:       map( int,
         | vec( float ) ) our_map;       init( &our_map );              //
         | Create a vector of floats:       vec( float ) our_vec;
         | init( &our_vec );       push( &our_vec, 1.23f );
         | // Insert the vector into the map.       insert( &our_map, 456,
         | our_vec );              // Generates a warning because get
         | checks its first argument for side       // effects and the
         | compiler can't tell that the first argument of the       //
         | outermost get has none:       printf( "%f", *get( get(
         | &our_map, 456 ), 0 ) );              // The "proper", albeit
         | cumbersome, way to achieve the same thing without a       //
         | warning:       vec( float ) *itr = get( &our_map, 456 );
         | printf( "%f", *get( itr, 0 ) );
        
       | rurban wrote:
       | The macro should be called IS_CONST(), not C()
        
         | immibis wrote:
         | To determine if an expression is constant in C, one must
         | determine if an expression in C is constant.
        
         | atiedebee wrote:
         | I agree, but for a blog post it is more concise (IS_CONST or
         | anything that is long would take up a lot more screen real
         | estate on my phone).
        
       | uecker wrote:
       | One can use _Pragma inside macros
        
       | listeria wrote:
       | Apparently the static_assert trick doesn't work with GCC, it just
       | compiles it with a warning if it's not a constant expression:
       | warning: expression in static assertion is not an integer
       | constant expression [-Wpedantic]
       | 
       | Instead you can use the sizeof + compound literal with array
       | type, use the comma operator to preserve the type of the
       | expression and cast the result of sizeof to void to suppress the
       | warning:                 #define C(x) ( (void)sizeof( (char
       | [(int)(x) || 1]){0} ), (x) )
       | 
       | The only problem is that it doesn't support floating point
       | expressions
        
       | hermitdev wrote:
       | > And I'd rather keep the library warning free instead of telling
       | the users to switch warnings off.
       | 
       | Thank you! Separately, but related: fuck you, Google! (every time
       | I have to deal with protobuf in C++, I curse Google and their "we
       | don't give a shit about signed vs unsigned comparisons").
        
         | fluoridation wrote:
         | I just turn warnings off for protobuf stuff. In general I do
         | that for any code I don't own but have to compile.
        
       | variadix wrote:
       | __builtin_choose_expr can be used instead of a ternary to avoid
       | the type conversion rules that require the typeof cast
        
       | fuhsnn wrote:
       | It's great of programmers to aim for portability, but frankly
       | it's kind of a stretch that an arbitrary C compiler that is
       | limited in standard support would the same time be sophisticated
       | enough to process these tricks as intended.
       | 
       | In my fork of chibicc (a small C11 compiler) there are plenty of
       | additional logic that were implemented solely to play nice with C
       | tricks from real world projects that could have been easier if
       | they target later standards. The most recent being how curl's
       | build script determines the size of primitive types: they use
       | (sizeof(T) == N) as a case index and expect the compiler to error
       | on case-duplication[1], I had to add a backtracking loop to check
       | exactly that[2]. I'm not complaining as more error checks isn't a
       | bad thing, however, I'll advise programmers willing to invest in
       | obscure tricks to actually test them on obscure compilers
       | (instead of just flipping -std).
       | 
       | [1]:
       | https://github.com/curl/curl/blob/339464432555b9bd71a5e4a4c4...
       | 
       | [2]:
       | https://github.com/fuhsnn/slimcc/blob/54563ecae8480f836a0bb2...
        
         | listeria wrote:
         | If the goal of testing on obscure compilers is to enhance such
         | compilers then I'm all for it. But I don't see much value in
         | having to dance around implementation details to support a
         | compiler which isn't standards compliant. Ideally standards
         | conforming code should just work, that's the point of
         | conforming to a standard.
        
           | immibis wrote:
           | Depends if you want people to be able to use your library
           | with those compilers or not. If it's free software, fine.
           | Don't fire well-paying customers though.
        
       | dandersch wrote:
       | > _And I 'd rather keep the library warning free instead of
       | telling the users to switch warnings off._
       | 
       | Why not push/pop warnings to ignore in the library?
       | _Pragma("GCC diagnostic push")       _Pragma("GCC diagnostic
       | ignored \"-Wshadow\"")       int a = 1;       {           int a =
       | 2;       }       _Pragma("GCC diagnostic pop")
        
         | kevin_thibedeau wrote:
         | This sort of thing is better set in CMake or equivalent with
         | file specific flags to disable diagnostics. Then you don't have
         | non-portable cruft littering the code, you don't have to touch
         | third party code, and there is a more centralized accounting of
         | what marginal code you're hacking around. The loss of
         | specificity is rarely going to be a problem.
        
           | cperciva wrote:
           | If you disable warnings in your makefile, you'll lose them
           | for the entire C file. Pragma warnings as above allow them to
           | be disabled for just the problematic code in question.
        
           | einpoklum wrote:
           | > with file specific flags
           | 
           | But you don't want to disable warnings throughout the file,
           | just locally.
           | 
           | > Then you don't have non-portable cruft littering the code,
           | 
           | You can make it perfectly portable, with a little macro work.
           | See, ,for example:
           | 
           | https://github.com/eyalroz/printf/blob/013db1e345cbb166a7eb7.
           | ..
           | 
           | (this is from the standalone-no-libc-printf-family-
           | implementation I maintain; the library is C, the test suite
           | is C++ but it doesn't matter for the purposes of my point
           | here.)
           | 
           | and that you only need to do once in your project (you can
           | make it your own header). Even when it comes to use - you can
           | place your warning-disabling within another macro, so that
           | you end up only using a simple single macro for your actual
           | code.
        
         | o11c wrote:
         | This probably isn't relevant anymore, but for now-old (4.x I
         | think) versions of GCC, there are a couple of caveats:
         | 
         | * Some versions can only change compiler options at top level,
         | not within a function.
         | 
         | * I had problems with trying to push/ignore/pop a warning
         | around an expression in a macro, since the entire thing expands
         | at a single location.
        
       | gitroom wrote:
       | This takes me back - all those compiler hacks just to keep stuff
       | portable kinda drive me nuts tbh. I love seeing people push for
       | warning-free code, though.
        
       | jesse__ wrote:
       | > And this cannot be silenced with #pragma since it's a macro, so
       | the warning occurs at the location where the macro is invoked.
       | 
       | I seem to remember there's actually a solution for this .. at
       | least on clang and I think MSVC .. you can programmatically turn
       | warnings on/on with the _Pragma() macro. I don't remember exactly
       | what you put in it, but it's designed specifically for this kind
       | of macro nonsense
        
       | _sbrk wrote:
       | gcc will not let you actually define a negatively-sized array.
       | Check it with some simple code -- I did. Even with -Wall -Wextra
       | -O1 -std=c11 -Wpedantic, if I actually try to create foo[-1], on
       | the stack or in BSS, I get the proper error: error: size of array
       | 'foo' is negative
        
       | o11c wrote:
       | Semi-related: given an expression which is an integer constant,
       | convert it to a statically-allocated char array. With appropriate
       | sigils this can be extracted via `strings(1)`, even when cross-
       | compiling.
       | 
       | If you don't know what type of integer your preprocessor is using
       | for arithmetic, you can still do right-shifts by up to 14 at a
       | time, since `int` must be at least 16 bits and you can't use the
       | sign bit.
        
       ___________________________________________________________________
       (page generated 2025-04-22 23:01 UTC)