[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)