The Best and Worst GCC Compiler Flags For Embedded | Interrupt

Compilers have hundreds of flags and configuration settings which can be toggled to control performance optimizations, code size, error checks and diagnostic information emitted. Often these settings wind up being copy and pasted from project to project, makefile to makefile, but it’s good to periodically audit the current options selected for a project.


This is a companion discussion topic for the original entry at https://interrupt.memfault.com/blog/best-and-worst-gcc-clang-compiler-flags

This is a gold mine for young developers like me (same as the other articles), basically saves us days or months of sailing through documentation. just wanted to thank you for the hard work.

1 Like

Great post! Regarding struct packing, another common use case in embedded is when defining a struct to overlay memory for a peripheral (or similar). If the compiler adds padding to the struct definition, the memory overlay will be incorrect when using it to access the underlying peripheral registers. This can lead to all sorts of problems at runtime that can be tough to track down.

1 Like

I have a piece of fake code (I need it to generate the errors) that I don’t know how to refactor to avoid errors.

#include <stdint.h>

uint8_t func(uint16_t val)
{
	uint8_t r;
	val += 0x7F;
	r = val >> 8;
	return r;
}

The error is the same you get:

$ gcc -Wall -Wextra -Wconversion -O0 module.c -c -o module.o 
module.c: In function 'func':
module.c:6:9: warning: conversion from 'int' to 'uint16_t' {aka 'short unsigned int'} may change value [-Wconversion]
    6 |  val += 0x7F;
      |         ^~~~
module.c:7:6: warning: conversion from 'int' to 'uint8_t' {aka 'unsigned char'} may change value [-Wconversion]
    7 |  r = val >> 8;
      |      ^~~

I’ve tried many ways without succeeding.

how would you do it?

Best regards
Max

Unfortunately, I think your only option here is to cast.

uint8_t func(uint16_t val)
{
	uint8_t r;
	val += (uint16_t)0x7F;
	r = (uint8_t)(val >> 8);
	return r;
}

already tried:

#include <stdint.h>

uint8_t func(uint16_t val0, uint16_t val1, uint16_t val2)
{
	uint8_t r;
	val0 += 0x7F;
	val1 += (uint16_t)0x7F;
	val2 += 0x7FU;
	r = (uint8_t)((val0+val1+val2) >> 8);
	r <<= (uint8_t)(val2 & 0x3);
	return r;
}

Here are the errors I get:

$ gcc -Wall -Wextra -Wconversion -O0 module.c -c -o module.o 
module.c: In function 'func':
module.c:6:10: warning: conversion from 'int' to 'uint16_t' {aka 'short unsigned int'} may change value [-Wconversion]
    6 |  val0 += 0x7F;
      |          ^~~~
module.c:7:10: warning: conversion from 'int' to 'uint16_t' {aka 'short unsigned int'} may change value [-Wconversion]
    7 |  val1 += (uint16_t)0x7F;
      |          ^
module.c:8:10: warning: conversion from 'unsigned int' to 'uint16_t' {aka 'short unsigned int'} may change value [-Wconversion]
    8 |  val2 += 0x7FU;
      |          ^~~~~
module.c:10:8: warning: conversion from 'int' to 'uint8_t' {aka 'unsigned char'} may change value [-Wconversion]
   10 |  r <<= (uint8_t)(val2 & 0x3);
      |        ^

In this post it is said that if I use -Wconversion I can no longer use compound assignment operator. Is that true?
I understand that they advise against the use of -Wconversion because it brings more problems than benefits.
In an averagely large project I guess that the false positives are many compared to the real bugs in the code base.

I misread the error, it’s the += that it’s objecting to.

Here’s the fixed version:

uint8_t func(uint16_t val)
{
	val = (uint16_t)(val + 0x7F);
	uint8_t r = (uint8_t)(val >> 8);
	return r;
}

compiled it with:

$ gcc-9 --version
gcc-9 (Homebrew GCC 9.2.0_2) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gcc-9 -Wall -Wextra -Wconversion -O0 test.c
$

Hi, Fantastic post!!!
I adopted a lot of the flags pointed to by this post for my project. I have several bit fields in structures. I am running into issues with bit fileds and -Werror=conversion flag. An example is shown below:

typedef struct led_control 
{
    uint8_t LED_IND : 1;
    uint8_t ICL_IND : 2;
    uint8_t LED_DRV : 1;
    uint8_t ICL_DRV : 2;
}led_control;

I get an error when i do something like this,

void optical_setDrvCurrent(uint8_t current, optical* opt)
{
    opt->_led_control.ICL_DRV = current;
    optical_virtualWrite(AS726X_LED_CONTROL, get_led_control(&opt->_led_control));
}

The compile error points to the opt->_led_control.ICL_DRV = current; statement.
error: conversion from 'uint8_t' {aka 'unsigned char'} to 'unsigned char:2' may change value [-Werror=conversion]

The only way to get past these errors (I use -Werror flag too) is to use bit masks and shifts, but that completely defeats the purpose of the bit field. Any thoughts on how to work around this?

Thanks,
Rajah

1 Like

@mcrajah Unfortunnately strict aliasing does not play nice with bitfields. Here’s a tickets against GCC on the topic: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=39170.

In the meantime, I think something like this should work to suppress the warning:

#define LED_DRV_WIDTH 1
#define LED_DRV_MASK (1 << LED_DRV_WIDTH - 1)

struct {
   uint8_t LED_DRV : LED_DRV_WIDTH;
} led_control;
...
opt->led_control.LED_DRV = current & LED_DRV_MASK;

The key here is that the compiler won’t complain if you mask the bits appropriately. No need for shifting though!

Thanks for the pointer @francois, I will try this to see if the warnings go away. Its better than shifting.

-Wunused-parameter is part of -Wall, so how do I turn it off if I’m already using -Wall?

-Wunused-parameter is part of -Wall, so how do I turn it off if I’m already using -Wall?

@pksublime you would need to explicitly opt out by adding -Wno-unused-parameter after -Wall in your list of compiler flags

1 Like

Hello!
Thanks for interesting article. I have added most of the flags for the current embedded project I am currently working. But I have hit a snag when adding the flag -Wdouble-promotion.

Since printf() seems to not have a formatting flag for float, only double, I get warnings (aka errors) when trying to print out a float. It is all defined in the attribute __format__, at least for GCC. Not easy to go around it with any tricks. :wink:

Is there a way around this?

Very nice article, thanks!
Just a small hint on -Wdouble-promotion for sake of correctness.
Some Cortex-M7 have double precision FPU, like the NXP i.MX RT 1064 I’m working with.
Cheers.
Alex.

1 Like

Really great post, you should tag it with “fw-code-size” or another tag in order to make it available via the “Tags” menu! Thanks

Some flags that should also be mentioned together with -ffunction-sections and -fdata-sections are their counterparts -mpragma-function-sections and -mpragma-data-sections: When using #pragma section for explicit memory mapping, having the data or function postfix is still very useful, and the -ffunction-sections and -fdata-sections do not apply to sections that are declared using a #pragma. I’ve stumbled upon this in a project where each and every variable/constant/text is placed explicitly.

This is a true gem, thanks for sharing :slight_smile:

Thanks for this quality blog post.
Notice it is -fshort-enums and not -fshort-enum.

We came to a similar conclusions over almost all of mentioned flags.
And here’s the current set of warnings we treat as errors.
No code with any of such warnings is safe enough to even start testing.
Until you’re ready to swith to -Werror, this set is a great place to be.

-Werror=array-bounds
-Werror=implicit-function-declaration
-Werror=incompatible-pointer-types
-Werror=int-conversion
-Werror=int-to-pointer-cast
-Werror=logical-op
-Werror=sizeof-pointer-div
-Werror=shadow
-Werror=sign-compare
-Werror=undef