Page 1 of 1

SystemClock_Config() and extern "C"

Posted: Sat Mar 08, 2025 3:00 am
by ozcar
The question of how to change the default system clock setup has come up here many times. The advice normally given is to define a SystemClock_Config() function with extern "C" in the sketch .ino file. That is really just repeating what is says here https://github.com/stm32duino/Arduino_C ... ock_config for those who have missed seeing that.

I was looking at this after I myself gave the advice about the extern "C" on SystemClock_Config() in a recent thread here (viewtopic.php?t=2578). I was a bit surprised to discover, when I tested it, that the extern "C" was not actually required. Regardless of whether I have that on my SystemClock_Config() or not, it still gets called automatically.

What is the true story? Was the extern "C" required at some time, but not anymore? Or, maybe it is still required, but not in all situations?

Re: SystemClock_Config() and extern "C"

Posted: Sat Mar 08, 2025 9:09 am
by fpiSTM
Normally, It is required when function is in a cpp file else it is decorated.

Re: SystemClock_Config() and extern "C"

Posted: Sat Mar 08, 2025 11:40 am
by ag123
I think extern "C" is mainly related to name mangling
https://web.mit.edu/tibbetts/Public/ins ... gling.html

if a *compiled* c library calls a c++ function, it likely needs extern "C".
however, if that library is compiled from source together with the main program codes, chances that it'd just works in C++ without that extern "C".
But that if it is used as a library instead, name mangling may cause symbol not found errors when compiled in C.

Re: SystemClock_Config() and extern "C"

Posted: Sat Mar 08, 2025 2:32 pm
by fpiSTM
In that case, no error as a weak function exists.

Re: SystemClock_Config() and extern "C"

Posted: Sun Mar 09, 2025 1:13 am
by ozcar
I spent more time looking at this, and I think in future, if anybody is asking, I will say that the extern "C" should be used, but also point out that you might "get away with" not using that. So, basically, with the extern "C" it should always work, while without that, it might or might not work, depending on how the compiler implements name decoration/mangling (for which there is no standard).

What I am seeing is that for a function like SystemClock_Config(), which happens to take no parameters, and does not return anything, the name does not actually get decorated/mangled. For the board I was testing with, the default SystemClock_Config(), is in variant_PILL_F103Cx.cpp, which is obviously a .cpp file, but it has #ifdefs to put extern "C" {} around the definition of SystemClock_Config(). If I run arm-none-eabi-nm on variant_PILL_F103Cx.cpp.o, I get:

Code: Select all

00000000 R analogInputPin
00000000 R digitalPin
         U HAL_RCC_ClockConfig
         U HAL_RCC_OscConfig
         U HAL_RCCEx_PeriphCLKConfig
         U memset
00000000 W SystemClock_Config
The W there must be from the WEAK.

My test code was in sketch_mar3a.ino, from which the IDE creates sketch_mar3a.ino.cpp and compiles that into sketch_mar3a.ino.cpp.o. Running nm on that gives:

Code: Select all

00000000 W _gettimeofday
         U delay
         U digitalWrite
         U getCurrentMicros
         U getCurrentMillis
         U HAL_RCC_MCOConfig
00000000 T loop
         U pinMode
00000000 T setup
00000000 T SystemClock_Config
That output remains the same whether I have the extern "C" on SystemClock_Config() or not. You can also see that the names for setup() and loop() which also happen to take no parameters and don't return anything, are also not decorated/mangled. In contrast, if I add functions which return something, or have parameters, I can see that the names get decorated/mangled with prefixes and suffixes when extern "C" is omitted.

Incidentally, in doing all this, I ran into some trouble due to the prototypes/declarations that the Arduino IDE adds to the .cpp verion of the sketch that gets compiled. For example when I added this to my sketch:

Code: Select all

extern "C" int SystemClock_Configy()           
{
 return 0;
}
The IDE inserted a prototype/declaration for SystemClock_Configy(), but WITHOUT the extern "C". I don't know why it strips the extern "C" off, but it resulted in the compiler complaining about conflicting declaration.

For the record, I am using Windows 11, Arduino IDE 2.3.2 and STM32duino 2.9.0.

Re: SystemClock_Config() and extern "C"

Posted: Mon Mar 17, 2025 7:45 pm
by ozcar
In a currently active thread here, somebody had defined a DMA irq routine without extern "C". I was just going to advise them to add that, but I was wondering if that was also something you might get away with. So I decided to check.

They had both prototype/declaration and definition without the extern "C":

Code: Select all

void DMA1_Channel1_IRQHandler();
...
void DMA1_Channel1_IRQHandler(void) {
   ...
}
(They were using the wrong DMA channel, but that is another matter.)

nm shows that name gets mangled to:

Code: Select all

00000000 T _Z24DMA1_Channel1_IRQHandlerv

So that is not going to replace the existing weak version, which is in the startup assembly code.

What I don't understand is why that name gets mangled, while SystemClock_Config does not (even if defined in the same sketch). Maybe there is some other declaration/prototype for one of them, but not the other in some header file?

The advice stays the same, to use the extern "C", but I still don't know why in some cases the code still works with or without it.

Any ideas?

Re: SystemClock_Config() and extern "C"

Posted: Tue Mar 18, 2025 5:41 am
by ozcar
ozcar wrote: Mon Mar 17, 2025 7:45 pm
What I don't understand is why that name gets mangled, while SystemClock_Config does not (even if defined in the same sketch). Maybe there is some other declaration/prototype for one of them, but not the other in some header file?
It appears to be due to this:

Code: Select all

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

// weaked functions declaration
void SystemClock_Config(void);
...
in ...\STMicroelectronics\hardware\stm32\2.10.1\libraries\SrcWrapper\inc\stm32_def.h

Given this comes up quite often, would it destroy some vast eternal plan if irqs and any other functions that need the extern "C" were also in that header (or maybe some other header that is always included)?