Generating custom compile errors in GCC C - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: July 25, 2023, 09:43:57 am
content: I'm writing setup code and from experience I can be prone to doing things like allocating the same pin or counter twice. To stop this I have written code that will track what resources have been used and send the program into a while loop where I will find it with a debugger. But I would rather catch these issues at compile time. Is there a way to generate custom compile errors in GCC ?I know that I can do this with the pre-processor but I want to avoid lots of macros.

author: mfro
author_rank: Regular Contributor
timestamp: July 25, 2023, 10:11:49 am
content: Quote from: Simon on July 25, 2023, 09:43:57 amin C11, there isstatic_assert()(or_Static_assert(), respectively). For anything earlier, you'll be bound to the preprocessor.

author: DiTBho
author_rank: Super Contributor
timestamp: July 25, 2023, 11:39:15 am
content: Quote from: Simon on July 25, 2023, 09:43:57 amhow do you allocate pins?

author: ejeffrey
author_rank: Super Contributor
timestamp: July 25, 2023, 11:52:56 am
content: You can use #error from the preprocessor for instance but the hard part is doing to be actually detecting the problem at compile time which depends on exactly what a bug looks like.Are you looking for something like calling the same function with the same constant argument from two call sites regardless of control flow? You might just be able to detect this with grep and a custom build check and avoid the compiler altogether.

author: Psi
author_rank: Super Contributor
timestamp: July 25, 2023, 11:57:14 am
content: You can use #warning and #errorThey will appear in your compile messages and you can add a text string after that will show up as well.Error will cause the compile to fail. Warning will just show a message as a warning but still compile.You can use them to make notes as you code that will show up later when you compile.eg#warning I should get around to improving this function some timeor#error Reminder, this block of code must be fixed before it's going to workOr you can combine with #if or #ifdef to create some checks that happen every time you compileto ensure your defines are the way you expected.eg#define SettingNum 123#if (defined SettingNum ) && (SettingNum > 255)#error The SettingNum feature was only written to support bytes, max 255#endif#if ( ! defined SettingNum )#warning SettingNum is not defined, did you mean to do that#endif#if (defined SettingNum ) && (SettingNum < 0)#error WTF are you doing making SettingNum negative, PEBKAC#endif

author: Simon
author_rank: Unknown
timestamp: July 25, 2023, 02:43:15 pm
content: Quote from: DiTBho on July 25, 2023, 11:39:15 amCode:static uint32_t port_pin_in_use ;void allocate_port_pin(uint8_t port_pin){uint8_t port = port_pin >> 5 ;uint8_t pin = port & 0b11111 ;if (port_pin_in_use & 0x1 << pin){while (1){; // this is the second time you are using this pin}}else port_pin_in_use |= 0x1 << pin ;}The port pin argument is simply an enumeration of PA00, PA01.......PB00, PB01.... to numbers starting at 0

author: Simon
author_rank: Unknown
timestamp: July 25, 2023, 02:58:40 pm
content: _Static_assert(0,"test") ; Works indeed. Perfect. What confuses me is why have the changed the name between C11 and C23? I know, I can define one as the other...... Maybe I need a pre-processor flag, you never escape the pre-processor......

author: Psi
author_rank: Super Contributor
timestamp: July 25, 2023, 10:43:28 pm
content: If you move all your pin/port defines into their own hardware port config file its easier to manage and see that there are no duplicates.eg#define LED_PIN PA00#define LED_PIN_BV _BV(LED_PIN)You can also have a macro #define to init the data direction and setup the pin.#define INIT_LED do { DDRA |= LED_PIN_BV; PORTA &= ~LED_PIN_BV; } while (0)And when you make a new revision of the PCB that has moved IO around a little all you need to do is sub-out for a different config file. Which can be done with a simple define for the board version.

author: ejeffrey
author_rank: Super Contributor
timestamp: July 26, 2023, 01:18:50 am
content: Quote from: Simon on July 25, 2023, 02:58:40 pmIt's for backwards compatibility. In c11 _Static_assert was the keyword, but if you include assert.h you get static_assert as a defined macro. This avoids clobbering existing code that used identifiers with that name. After 10 years to allow migration the requirement for the header was removed.

author: Simon
author_rank: Unknown
timestamp: July 26, 2023, 09:58:11 am
content: I see, I guess the microchip compiler is behind then and must be C11 even though up to date.

author: Simon
author_rank: Unknown
timestamp: July 26, 2023, 01:45:51 pm
content: Oh well, that was short lived, nothing works, it just keeps making stupid demands, apparently the value tested has to be a constant. Very useful, not assert.h no longer has it.

author: ejeffrey
author_rank: Super Contributor
timestamp: July 26, 2023, 04:09:42 pm
content: Yeah it's not possible to tell at compile time how many times a given function is called. static_assert only works expressions that can be evaluated at compile time. I'm not sure how you expected that to work.Code:grep -c 'allocate_port_pin\(PA00\)' *.cand check for greater than 1 is probably about the best you can do. But it for sure can have false negatives and positives.

author: TheCalligrapher
author_rank: Regular Contributor
timestamp: July 26, 2023, 04:43:27 pm
content: Quote from: Simon on July 25, 2023, 02:58:40 pmSince the very beginning (C99) it was `_Static_assert`, plus a `static_assert` macro defined in <assert.h>. In C23 `static_assert` became a built-in language feature, while `_Static_assert` became deprecated (yet still supported). So, there should really be no observable "chahge of name" in real-life code.

author: Nominal Animal
author_rank: Super Contributor
timestamp: July 26, 2023, 08:05:16 pm
content: Here is a no-runtime-overhead approach that works:Use a specific section for the used pin numbers, and a preprocessor macro to declare the pin allocated:Code:#define PIN_SECTION "pins"#define MERGE4_(a,b,c,d) a ## b ## c ## d#define MERGE4(a,b,c,d) MERGE4_(a,b,c,d)#define NAMELINE(prefix) MERGE4(__, prefix, _, __LINE__)#define ALLOCATE_PIN(pin, message) \static const char NAMELINE(pin_alloc) \__attribute__((section (PIN_SECTION), used)) = pinint main(void){ALLOCATE_PIN(1, "First example");ALLOCATE_PIN(2, "Second example");}ALLOCATE_PIN(3, "Third example");The message is not recorded, it is only for the human programmers. An allocation can occur in any scope, even inside functions, even inside a loop iteration.Then, in your build, when all .o files have been compiled, you collect their pin allocations, and check if any pin is allocated more than once anywhere. Using Bash, od, tr, awk, and objcopy (included as part of the target binutils toolchain):Code:for file in *.o ; do objcopy -O binary -j pins "$file" /dev/stdout ; done | od -A none -t d1 | tr '\t\n\v\f\r ' '\n\n\n\n\n\n' | awk 'BEGIN { split("", pin) } NF==1 { pin[$1]++ } END { status=0; for (p in pin) if (pin > 1) { status=1 ; printf "Pin %d (0x%02x) used %d times!\n", p, pin, pin } exit(status) }'(This works on all targets and toolchains that use ELF object files, it is not Linux/BSD specific at all; I just used the tools I currently have for the latter example, because it was the quickest way.)As is, the above is just the proof of concept, allowing the macro use anywhere, but not recording the message. There is no runtime overhead at all, as long as the "pins" section is discarded from the final binary. The same approach can be used for resource allocation, even external memory ranges in EEPROMs and PSRAMs not mapped to the device address space, of course, just using a fixed-size structure instead of a single unsigned char.With a bit more complex example, using a fixed-size structure to include the pin, the pin name stringified, the file name (__FILE__macro), the line number (__LINE__), and the message, a slightly more complex check can describe the exact conflicting allocations, with absolutely no runtime overhead. The only overhead is increased build-time object file size, as they grow larger by the structures, but the final binary is not affected at all. I can write such a checker (reading objcopy output, or internally invoking objcopy) in C or Python 3 for example, but note that this would be run on the development machine at build time, not on the target device. Python 3 might be more portable, as I prefer C99 + POSIX interfaces. (I wanted to try keep this postshort, you see.)If you want to actually use the latter, I can create a larger example, and a suitable object file checker for you. It is not complicated at all, as you can put everything behind a single header file, and if you run the checker as part of the build process using make or cmake or derivatives, they will abort the build if the checker returns nonzero.

author: Simon
author_rank: Unknown
timestamp: July 27, 2023, 06:40:45 am
content: Let me guess, this is something that C++ just does.

author: DiTBho
author_rank: Super Contributor
timestamp: July 27, 2023, 08:38:50 am
content: Quote from: Nominal Animal on July 26, 2023, 08:05:16 pmBrilliant approach!You take advantage of the sections, which due to how the linker script is made can be "skimmed" (filtered) in the final executable.

author: Nominal Animal
author_rank: Super Contributor
timestamp: July 27, 2023, 02:35:52 pm
content: Quote from: Simon on July 27, 2023, 06:40:45 amI haven't seen anything like the no-runtime overhead ELF-section-based allocation I showed above, done in C++.Of course, the same works just fine in C++ as well (and even as-is, withinextern C { ... }).It is really not C or C-variant specific, as it only requires that the build toolchain uses ELF format object files, and that the C compiler supports the section attribute (which all C compilers producing ELF object files, including gcc, clang, Intel Compiler Collection, etc. that I have ever managed to test, do support).Quote from: DiTBho on July 27, 2023, 08:38:50 amExactly. The easiest way to do this is to add/DISCARD/ { *(pins) }at the beginning of the firstSECTIONSblock in your linker script.I do like the ELF section approach for all kinds of build-time checking and information collation.The same approach can be done for any kind of allocation, be they address ranges, interrupt numbers, or even strings (command and variable names used in a communication protocol with a host/device). You can obtain the raw data from an .o file usingobjcopy-O binary -j pinsfile.o /dev/stdout(using the specific objcopy for the target device, part of the build toolchain), so the filtering/checking program just needs to parse the binary contents back to the structures used; noting that the format and types are based on the target device, so you cannot assume the development machine has the same conventions (byte order in particular). The filtering/checking program can even sort and output derived data, say an array or index table for a menu (so you can create menu entries anywhere in code), and emit it as a C source file, or a binary data you insert into some object file withobjcopy-I binary ...Such compile-time allocators should not reference the address of a variable, though. This information is lost in the pure binary dumps of the section, as relocation information is separate.A few months ago I struggled with exactly this, as I have many use cases where specific ordering of the entries in the section would be really useful. For example, consider a firmware that communicates using some command-response protocol, where each source file could define new commands. If the command name and the handling function were recorded in such a structure, we could externally sort these into an optimum order; for example, into a perfect hash table with no collisions, or into alphabetic order so that binary search can be used. Or just a hash table with a fixed hash function. The order in which the linker combines the sections from different object files, is basically unpredictable (except with a specific version of a specific toolchain).The solution I found was to export all the variables and functions that are referenced (taken the address of in such structures), and then simply record their globalnameas a string in the section. Then, the filter/checker/sorter program just re-emits the section data as a C source file (that will be compiled last, only just before linking all together into the final binary), where the address reference is&name, withexterntypename;declaration for each before the array construct.typedoes differ for variables and functions, but in general, there is no need to be exactly correct with the type, as the type is not recorded in the ELF object file. That is, you can useextern charname;for all variables, and a single function pointer type for all functions, because the name is all that is recorded in the ELF file for C code.For perfect hashing, that C source file can also contain the perfect hashing algorithm adapted for that particular dataset, generated among candidate variable hash functions that you can brute-force check at filter/checker/sorter run time (build time for the target binary).For menus, you can have the section structure describe the chain or always-present main entry off which it'll hang off from, and perhaps a priority within that list, with the C source file exporting a graph with two-way links between each menu entry (along each dimension the user can travel, so usually two prev-next pairs of pointers to menu entries). That way, any place in your source files can export its controlling menu entry directly, with only the menu entry structure variable being exported (so nostaticin the macro shown in my previous message), and the checker/sorter program reconstructs the entire menu graph, perhaps even exporting it for documentation in Graphviz form. All that data will reside in Flash, and only the current menu entry pointer will be in RAM.I do love exploiting ELF sections to make my life as a software designer/engineer and developer easier. It is a pity they're not more widely exploited; they're an awfully powerful tool for build-time shenanigans!

author: westfw
author_rank: Super Contributor
timestamp: August 01, 2023, 12:02:13 am
content: I have this interesting hack that I've used. It puts functions that are defined with the "error" attribute in places where the compiler will eliminate them (due to optimization) when there is no problem.Code:boolean NonConstantUsed( void ) __attribute__ (( error("") ));void LTO_Not_Enabled(void) __attribute__ (( error("") ));#define digitalWriteFast(pin, val) \if (__builtin_constant_p(pin)) {_dwfast(pin, val);} else {NonConstantUsed();}static inline __attribute__((always_inline)) void _dwfast(int pin, int val) {uint8_t mask = digital_pin_to_bit_mask_PGM;uint8_t port = digital_pin_to_port_PGM;volatile uint8_t *outport = (volatile uint8_t*)port_to_output_PGM;if (digital_pin_to_bit_mask_PGM == 0x99) // Never trueLTO_Not_Enabled();if (val) {*outport |= mask;} else {*outport &= ~mask;}}Theif (digital_pin_to_bit_mask_PGM == 0x99) // Never trueis especially interesting. In the case where the array is available at compile time, the "if" can be evaluated at compile time as well, so there is no need to call the function. If it's external, the compiler will try to insert the function call, and cause the error.

author: 5U4GB
author_rank: Super Contributor
timestamp: August 05, 2023, 09:15:48 am
content: Quote from: mfro on July 25, 2023, 10:11:49 amYou can mostly emulate static_assert in pre-C11 using the preprocessor, if there's any interest in this let me know and I'll dig up the code.

author: Simon
author_rank: Unknown
timestamp: August 06, 2023, 03:14:50 pm
content: Well the preprocessor is easy as you use #warning and #error but I would be having to create a whole system of preprocessor code. This means that now I have to use the preprocessor to write a lot of the code, this makes it more complicated from what I have now. I will leave it alone. If anything one day I may use a serial port as standard to debug these things and output such messages rather than leave it stuck in the while loop as that can be made even more specific.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 06, 2023, 08:50:59 pm
content: static_assert is a great addition in C11 and later. Unless you are using an old compiler not supporting at least C11, there's little reason not to use it.If you're using GCC for any target that is currently maintained, then C11 support has been there for over ten years.(One reason you may not have access to C11 is if you use a proprietary compiler that doesn't support it, or if your current dev team's guidelines forces you to stick to C99 or even C89. If you're the decider, don't fret.)

author: westfw
author_rank: Super Contributor
timestamp: August 07, 2023, 05:05:55 am
content: Another possibility is to use inline asm.asm (at least with the gnu assembler) has "deeper visibility" into things like existing symbols and their values.For instance:Code:int main(void){asm(".ifdef main\n"".warning \"main is already defined, from ASM\"\n"".endif\n");#ifdef main#warning main is already defined, from C#endif}

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 07, 2023, 09:59:45 am
content: The interesting ones the Linux kernel has historically used:#define BUILD_BUG_ON_ZERO(expr) (sizeof(struct { int:(-!!(expr)); }))which causes a build-time failure ifexpris true ornonzero. This relies on C99. Whenexpris zero or false, it calculates the size of a structure with an unnamed bitfield of zero width (which indicates no more bitfields in the current unit), so normally the expression evaluates to zero. However, whenexpris nonzero, it tries to calculate the size of a structure with an anonymous bitfield of size -1 bits.The_ZEROsuffix is not an error: it does not mean that this does a build bug when the expression is zero, it refers to the fact that normally, this expression returns zero.#define BUILD_BUG_ON(expr) ((void)sizeof(char[1 - 2*!!(expr)]))also causes a build-time failure ifexprisnonzero. Whenexpris zero or false, it is simply an unused sizeof expression of a one-char array. Whenexpris true or nonzero, it becomes an unused sizeof expression of an array with -1 chars, and thus should lead to a compile-time error.#define BUILD_BUG_ON_INVALID(expr) ((void)(sizeof((long)(expr))))This verifies thatexpris valid, without evaluating any side effects inexpr. (For example,BUILD_BUG_ON_INVALID(x++)checks thatxis a valid variable, but does not increment it.)In any case, I do recommend usingstatic_assert(expr,message_string), with one of the header files containing#define static_assert(expr, msg) _Static_assert(expr, msg)and expand on that when you encounter compilers that do not support it. Each compiler does provide identifiablepre-defined macrosone can#ifdefabout, and implement a suitable workaround. Evensdcchas supported_Static_assertsince version 3.3.0 (released in 2013).If you want to future-proof that, useCode:#if !defined(static_assert)#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L#define static_assert(expr, msg) static_assert(expr, msg)#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L#define static_assert(expr, msg) _Static_assert(expr, msg)#else#define static_assert(expr, msg) ((void)sizeof(char[1 - 2*!(expr)]))#endif#endif /* !defined(static_assert) */which uses the__STDC_VERSION__pre-defined macro the compiler is supposed to set. If it is already a macro, it does not redefine it. If the compiler advertises itself as a C23 one, thenstatic_assertis used. If not, but the compiler advertises itself as a C11 one,_Static_assertis used. Otherwise, it falls back to evaluating the size of an array of chars with one element ifexpris true, and -1 elements ifexpris false (losing the message, though).That assumes C23 will have__STDC_VERSION__ == 202311L.