Issue
I did run into the situation, that I declared two (separate) global variables with the same name in two separate files, but without using static
, volatile
nor extern
on them.
One file was a .c and the other a .cpp file.
The compiler and build environment (GCC) was the ESP IDF and even the data types were different on these declarations.
mqtt_ssl.c:
esp_mqtt_client_handle_t client;
mb_master.cpp:
PL::ModbusClient client(port, PL::ModbusProtocol::rtu, 1);
During the runtime i experienced a lot of problems with reboots of the ESP32 until I found out, that the same name, of two actually separate variables, is causing the issue.
My guess is, that the compiler used the same memory-region for both of them.
I expected, that it gets handled as two separate objects with its own region in the memory, which is obviously not true.
After reading some questions here and there is my understanding now is that the behavior is undefined, if a variable with the same name gets declared without static
, extern
or volatile
in two separate C/C++ files.
It took me quite a while to figure that out.
If it is not allowed to declare it like this, why didn't the compiler/linker throw an error?
Is there an option for GCC to treat such a situation as error to prevent that situation in the future?
Edit 1:
This is a reproducible example with xtensa-esp32-elf-gcc.exe (crosstool-NG esp-2021r2-patch3) 8.4.0.
app_main.c
#include <stdio.h>
void test_sub(void);
uint16_t test1;
void app_main(void)
{
test1 = 1;
printf("app_main:test1=%d\n", test1);
test_sub();
printf("app_main:test1=%d\n", test1);
}
test_sub.c
#include <stdio.h>
int16_t test1;
void test_sub(void)
{
test1 = 2;
printf("test_sub:test1=%d\n", test1);
}
Result:
app_main:test1=1
test_sub:test1=2
app_main:test1=2
test1
in app_main()
got overwritten by test_sub()
, because it had the same name in both files.
Solution
esp_mqtt_client_handle_t client;
is a tentative definition. In spite of its name, it is not a definition, just a declaration, but it will cause a definition to be created at the end of the translation unit if there is no regular definition in the translation unit.
The C standard allows C implementations to choose how they resolve multiple definitions (because it says it does not define the behavior, so compilers and linkers may define it). Prior to GCC version 10, the default in GCC was to mark definitions from tentative definitions as “common,” meaning they could be coalesced with other definitions. This results in the linker not complaining about such multiple definitions.
Giving the -fno-common
switch to GCC instructs it not to do this; definitions will not be marked as “common,” and the linker will complain if it sees multiple definitions.
Answered By - Eric Postpischil Answer Checked By - Robin (WPSolving Admin)