我该如何阻止g++链接不需要的异常处理代码?

6
我正在使用GCC/G++编译器为arm-eabi开发嵌入式应用程序。由于资源限制,我试图禁用标准C++异常处理。我使用“-fno-exceptions -nostartfiles -ffreestanding”编译代码。
当一个类的全局实例存在,并且该类包含另一个类作为成员时,会链接大量的异常处理代码。这本身并不是太糟糕,但它还会带来许多stdio相关的内容,如printf、fopen、fclose和其他FILE函数。这个应用程序没有文件系统,即使有,这些函数也会浪费太多的代码空间。
我知道即使使用-fno-exceptions,G++仍然链接了一个使用异常的operator new,因为库没有非异常使用的operator new(除了new(nothrow))。我创建了operator new和delete的替代方法,并将其与不需要的标准库函数一起链接到输出中。
令我困惑的是,我没有在任何地方调用new。只有当全局对象包含另一个对象时,才会链接所有这些代码。
例如:
class UartA {
...
private:
  Ringbuffer* rxbuf;
};

class UartB {
...
private:
  Ringbuffer rxbuf;
};

如果创建了UartA的全局实例,则不会链接异常处理、operator new和stdio代码。这正是我想要的。
如果创建了UartB的全局实例(其中rxbuf是实例而不是指针),那么不需要的代码将被链接进来。
UartA和UartB都不使用operator new、exceptions或stdio。它们仅在rxbuf的类型上有所不同。
您能否建议如何防止链接额外的代码?同时,为什么对UartB进行了链接,但没有对UartA进行链接?

需要明确的是,该代码使用相同的“-fno-exceptions -nostartfiles -ffreestanding”标志进行编译和链接。其中也包括“-fno-rtti”,但不应与此问题有关。 - Neil
3个回答

6

因为您基本上正在执行操作系统开发人员将要执行的操作,以获取独立的C或C++环境。您可能需要考虑使用自定义链接器脚本。只需小心,因为全局构造函数不再自动发生..但您也不会得到任何您没有明确请求的内容(编写调用全局构造函数的代码并不难)。这是我的操作系统中的链接器脚本。

OUTPUT_FORMAT("elf32-i386")
ENTRY(start)

virt = 0xc0100000; /* 3.1 gig */
phys = 0x00100000; /* 1 meg */

SECTIONS
{ 
    .text virt : AT(phys) 
    {
        code = .; _code = .; __code = .;
        *(.text)
        *(.gnu.linkonce.t*)
        . = ALIGN(4096); 
    }

    .rodata : AT(phys + (rodata - code))
    {
        rodata = .; _rodata = .; __rodata = .;
        *(.rodata*)
        *(.gnu.linkonce.r*)
        __CTOR_LIST__ = .;
        LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2) 
        *(.ctors) 
        LONG(0) 
        __CTOR_END__ = .; 

        __DTOR_LIST__ = .; 
        LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2) 
        *(.dtors) 
        LONG(0) 
        __DTOR_END__ = .; 
        . = ALIGN(4096); 
    }

    .data : AT(phys + (data - code))
    {
        data = .; _data = .; __data = .;    
        *(.data)
        *(.gnu.linkonce.d*)
        . = ALIGN(4096); 
    }

    .tbss : AT(phys + (tbss - code)) 
    {
        tbss = .; _tbss = .; __tbss = .;
        *(.tbss)
        *(.tbss.*)
        . = ALIGN(4096); 
    }

    .bss : AT(phys + (bss - code)) 
    {
        bss = .; _bss = .; __bss = .;
        *(.bss)
        *(.bss.*)
        *(COMMON)
        *(.gnu.linkonce.b*)
        . = ALIGN(4096); 
    }

    end = .; _end = .; __end = .;
}

它可能做的比你需要的要多(将部分对齐到4k边界,所有符号都在大于3GB的标记处),但是这是一个很好的起点。

你可以像这样使用它:

ld -T link_script.ld *.o -lc -o appname

如果您想链接libc,那么“-lc”也应该链接到libc。

5

我认为最接近的方法是使用-fno-exceptions和-fno-rtti编译并链接。如果有更好的方法来摆脱其他问题,我也很乐意听取建议。

至于如何摆脱new命令,请尝试使用-nostdlib选项。


谢谢 Dan,-nostdlib确实可以防止不需要的东西,但它也会阻止所有标准库的链接,包括像memcpy这样的函数。我猜我可以使用-nostdlib,并创建自己的C库。只是似乎不应该这样做。告诉编译器和链接器我不想要异常或启动文件,并且应用程序是脱机的(没有操作系统),应该足够让它按照我的意愿执行。 - Neil
1
我相信你可以使用-nostdlib选项,手动链接你需要的库(例如,避免链接数学库,如果你不需要的话)。 - David Rodríguez - dribeas
2
是的,如果你不想使用libstdc++,可以结合使用-nostdlib和-lc、-lm等选项。 - Dan Olson
6
我想我解决了这个问题。全局对象使用atexit()安排调用它们的析构函数,atexit()又调用__register_exitproc,而后者会调用malloc。关键点是,尽管实际对象的存储由链接器分配,全局对象在析构函数中需要使用malloc,即析构函数需要动态内存分配。因此,将链接命令更改为包括"-nostdlib /path/to/my/malloc.o -lc",可以强制将我的malloc链接到标准C库之前。我认为我也需要在某个时候添加"-lstdc++"。我误以为异常处理已经包含在内了。 - Neil

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接