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?
SystemClock_Config() and extern "C"
Re: SystemClock_Config() and extern "C"
Normally, It is required when function is in a cpp file else it is decorated.
Re: SystemClock_Config() and extern "C"
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.
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.
Last edited by ag123 on Sun Mar 09, 2025 3:17 am, edited 1 time in total.
Re: SystemClock_Config() and extern "C"
In that case, no error as a weak function exists.
Re: SystemClock_Config() and extern "C"
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:
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:
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:
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.
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
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
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;
}
For the record, I am using Windows 11, Arduino IDE 2.3.2 and STM32duino 2.9.0.
Re: SystemClock_Config() and extern "C"
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":
(They were using the wrong DMA channel, but that is another matter.)
nm shows that name gets mangled to:
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?
They had both prototype/declaration and definition without the extern "C":
Code: Select all
void DMA1_Channel1_IRQHandler();
...
void DMA1_Channel1_IRQHandler(void) {
...
}
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"
It appears to be due to this: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?
Code: Select all
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// weaked functions declaration
void SystemClock_Config(void);
...
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)?