EDIT: Please scroll down to the "EDIT" section at the end of the question for more recent details. I'm not editing the rest of this post to preserve history of the comments.
I have a class defined like so in a header file:
class TestClass
{
public:
TestClass() { }
~TestClass() { }
void Test();
private:
static const char * const carr[];
static const int iarr[];
};
The TestClass::Test()
function just makes sure that both arrays are used so they're not optimized away - prints them to log. I won't post it here for clarity. Arrays are initialized in .cpp file.
Above case works fine, when creating an instance of this class, the addresses look like so:
t TestClass * 0x20000268
carr const char * const[] 0x8002490 <TestClass::carr>
iarr const int [] 0x800249c <TestClass::iarr>
Memory addresses beginning with 0x20...
belong to RAM region, while 0x80...
belong to ROM/Flash. As expected, both arrays get placed in ROM.
However if I add virtual
qualifier to any function in the class, e.g. its destructor like so:
class TestClass
{
public:
TestClass() { }
virtual ~TestClass() { }
void Test();
private:
static const char * const carr[];
static const int iarr[];
};
Then the result is this:
t TestClass * 0x20000268
carr const char * const[3] 0x80024b4 <TestClass::carr>
iarr const int [1000] 0x20000270
In particular - iarr
is placed in RAM which is totally not what I expected.
This file is compiled like so:
arm-none-eabi-g++ -mcpu=cortex-m7 -mthumb -mfloat-abi=soft -O0 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -fno-move-loop-invariants -Wall -Wextra -g3 -DDEBUG -DUSE_FULL_ASSERT -DTRACE -DOS_USE_TRACE_ITM -DSTM32F767xx -DUSE_HAL_DRIVER -DHSE_VALUE=24000000 -I../include -I../system/include -I../system/include/cmsis -I../system/include/stm32f7-hal -std=gnu++11 -fabi-version=0 -fno-exceptions -fno-rtti -fno-use-cxa-atexit -fno-threadsafe-statics -c -o "src\\main.o" "..\\src\\main.cpp"
And the linking part:
arm-none-eabi-g++ -mcpu=cortex-m7 -mthumb -mfloat-abi=soft -O0 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -fno-move-loop-invariants -Wall -Wextra -g3 -T mem.ld -T libs.ld -T sections.ld -nostartfiles -Xlinker --gc-sections -L"../ldscripts" -Wl,-Map,"VirtualClassTestF7.map" --specs=nano.specs -o "VirtualClassTestF7.elf" "@objs.rsp"
There are more files built in this project, related to hardware initialization. I don't include those to keep the post short.
Is there any switch that controls this behavior? I've already tried the obvious parts that I could come up with, which could have a slightest connection with the issue:
-ffunction-sections
and -fdata-sections
-fno-common
Toolchain is arm-none-eabi
. Tried versions (outputs of arm-none-eabi-gcc --version
):
First one comes from official ARM website: https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads. Last two come from http://www.freddiechopin.info/en/download/category/11-bleeding-edge-toolchain as ARM officially doesn't release 64-bit version and our project grew to size that breaks the 32-bit version.
Why this is an issue and why I'm specifically looking into a compiler switch: Possibly there is another way to force those values into ROM by writing it a bit differently. This is not an option - we've encountered this issue recently in a larger project spanning thousands of files where class inheritance is used heavily in various places. Catching all the possible occurrences of such arrays (some being created with macros, some by external tools) and then reorganizing all that code is out of the question. Therefore I'm looking for a reason why the compiler behaves in this exact way and what are the possible solutions that don't involve touching the source files.
EDIT: It appears to be some kind of issue with gdb and how it retrieves the address of that variable, or I'm missing something. I went ahead and created the same example on PC (Cygwin gcc 7.4.0):
#include <stdio.h>
class TestClass
{
public:
TestClass() { }
virtual ~TestClass() { }
static const char * const carr[];
static const int iarr[];
};
const char * const TestClass::carr[] = {
"test1", "test2", "test3"
};
const int TestClass::iarr[] = {
1,2,3,4,5,6,7,8,9,0
};
int main() {
TestClass instance;
printf("instance: %p, carr: %p, iarr: %p\n", &instance, instance.carr, instance.iarr);
fflush(stdout);
while(1);
return 0;
}
The output of the program is this:
instance: 0xffffcba8, carr: 0x100403020, iarr: 0x100403040
This is also confirmed by the map file. Relevant portion:
.rdata 0x0000000100403000 0xa0 ./src/main.o
0x0000000100403020 TestClass::carr
0x0000000100403040 TestClass::iarr
However gdb shows this:
p instance.iarr
$2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
p &instance.iarr
[New Thread 57872.0x4f28]
$3 = (const int (*)[10]) 0x60003b8a0
p &instance.iarr
$4 = (const int (*)[10]) 0x60003b8d0
And what's even more interesting is that this address changes every time I attempt to print it with gdb. What's the reason for this?
Question title and tags adjusted.
gdb copies your array into RAM, you don't even need an instance for it, a class with a vtable is enough:
(gdb) p TestClass::iarr
$1 = {1, 2, 3, 4, 5, 6}
(gdb) p (int*)TestClass::iarr
$2 = (int *) 0x7ffff7a8b780
(gdb) p *(int *) 0x7ffff7a8b780 @ 100
$3 = {1, 2, 3, 4, 5, 6, 0 <repeats 94 times>}
(gdb) p (int*)TestClass::iarr
$4 = (int *) 0x7ffff7a8b7a0
(gdb) p (int*)TestClass::iarr
$5 = (int *) 0x7ffff7a8b7c0
(gdb) p *(int *) 0x7ffff7a8b780 @ 100
$6 = {1, 2, 3, 4, 5, 6, 0, 0, 1, 2, 3, 4, 5, 6, 0, 0, 1, 2, 3, 4, 5, 6, 0 <repeats 78 times>}
I guess this boils down to gdb's interpretation of "C". If you need the real address in gdb, you need a function that returns it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With