2.5.1. C 和 C++ 程序使用库函数的方式

本节介绍了以下内容:

初始化执行环境并执行应用程序

程序入口点位于 C 库中的 __main 处,库代码在此处执行以下操作:

  1. 将非根(RO 和 RW)执行区从其加载地址复制到执行地址。 另外,如果压缩了任何数据节,则会将它们从加载地址解压缩到执行地址。 有关详细信息,请参阅链接器和实用程序指南

  2. 将 ZI 区清零。

  3. 跳转到 __rt_entry

如果不希望库执行这些操作,您可以定义自已的 __main 以跳转到 __rt_entry,如Example 2.1 所示。

Example 2.1. __main 和 __rt_entry


    IMPORT __rt_entry	

    EXPORT __main	

    ENTRY	

__main

    B     __rt_entry	

    END

库函数 __rt_entry() 按以下方式运行程序:

  1. 调用 __rt_stackheap_init() 以设置堆栈和堆。

  2. 调用 __rt_lib_init() 以初始化引用的库函数、初始化区域设置以及为 main() 设置 argcargv(如果需要)。

    对于 C++,通过 __cpp_initialize__aeabi_ 为任何顶层对象调用构造函数。 有关详细信息,请参阅C++ 初始化、构建和析构

  3. 调用 main()(应用程序的用户级根)。

    main() 中,程序可以调用库函数等一些函数。 有关详细信息,请参阅从 main() 中调用的库函数

  4. 使用 main() 返回的值调用 exit()

C++ 初始化、构建和析构

C++ 对构建和析构具有静态存储时限的对象设定了一些要求。 请参阅 C++ 标准的第 3.6 节

ARM C++ 编译器使用 .init_array 区来实现此目的。 这是一个指向函数的自相关指针的常数数据数组。 例如,可以在 test.cpp 文件中包含以下 C++ 转换单元:


struct T

{

    T();

    ~T();

} t;



int f()

{

    return 4;

}



int i = f();

这会转换为以下伪代码:


         AREA ||.text||, CODE, READONLY

     int f()

    {

         return 4;

    }



     static void __sti___8_test_cpp

     {

         // construct 't' and register its destruction

         __aeabi_atexit(T::T(&t), &T::~T, &__dso_handle);

         i = f();

     }

         AREA ||.init_array||, DATA, READONLY

         DCD __sti___8_test_cpp - {PC}



         AREA ||.data||, DATA

     t   % 4

     i   % 4

此伪代码仅用于说明目的。 要查看生成的代码,请使用 armcc-c--cpp-S 编译 C++ 源代码。

链接器将不同转换单元中的每个 .init_array 收集在一起。 按相同顺序累积 .init_array 是非常重要的。

库例程 __cpp_initialize__aeabi_ 是在 main 之前从 C 库启动代码 __rt_lib_init 中调用的。 __cpp_initialize__aeabi_ 遍历 .init_array 以依次调用每个函数。 在出口处,__rt_lib_shutdown 调用 __cxa_finalize

通常,每个转换单元最多有一个用于 T::T() 的函数(重整名称为 _ZN1TC1Ev)、一个用于 T::~T() 的函数(重整名称为 _ZN1TD1Ev)、一个 __sti__ 函数以及 4 个字节的 .init_arrayf() 函数的重整名称为 _Z1fv。 无法确定转换单元之间的初始化顺序。

带有析构函数的函数局部静态对象也是使用 __aeabi_atexit 处理的。

某些节必须连续放置在同一区中,以便能够访问其 base/limit 符号。 否则,链接器将产生错误。

例如,C++ 编译器生成 .init_array 区。 必须将此区放在与 __cpp_initialize__aeabi_ 相同的区中。 为此,请在分散加载描述文件中指定节,例如:


LOAD_ROM 0x0000000

{

    EXEC_ROM 0x0000000

    {

        init_aeabi.o(+RO)

        * (.init_array)

        ...

    }

    RAM 0x0100000

    {

        * (+RW,+ZI)

    }

}

遗留对象支持

在 RVCT 2.1 或更高版本中,将 C$$pi_ctorvec 替换为 .init_array。 但是,仍然支持具有 C$$pi_ctorvec 的对象。 因此,如果有遗留对象,分散文件将包含:


LOAD_ROM 0x0000000

{

    EXEC_ROM 0x0000000

    {

        init_aeabi.o(+RO)

        init.o(+RO)              ; backwards compatibility

        * (.init_array)

        * (C$$pi_ctorvec)        ; backwards compatibility

        ...

    }

    RAM 0x0100000

    {

        * (+RW,+ZI)

    }

}

在 RVCT 中,C++ 名称重整方案与 ADS 1.2 版不同。 将新的 C++ 映像加载到旧调试器中时,这可能会产生影响。

异常系统初始化

可以在需要时(即在第一次使用时)初始化异常系统,也可以在进入 main 之前对其进行初始化。 在需要时进行初始化的优点是,除非使用异常系统,否则不分配堆内存;但缺点是如果在第一次使用时堆耗尽,则无法抛出任何异常(如 std::bad_alloc)。

缺省设置是在需要时进行初始化。 要在进入 main 之前初始化异常系统,请在链接中包含以下函数:


extern "C" void __cxa_get_globals(void);

extern "C" void __ARM_exceptions_init(void)

{

    __cxa_get_globals();

}

虽然可以将 __cxa_get_globals 调用直接放在代码中,但将它放在 __ARM_exceptions_init 中可确保尽早对其进行调用。 即,在初始化任何全局变量和进入 main 之前。

库初始化机制弱引用 __ARM_exceptions_init,并在将其作为 __rt_lib_init 的一部分时调用它。

Note

异常系统是通过调用不同库函数来进行初始化的,例如,std::set_terminate()。 因此,在进入 main 之前,可能不需要进行初始化。

用于异常的紧急缓冲区内存

可以选择是否分配紧急内存,它用于在堆耗尽时抛出 std::bad_alloc 异常。

要分配紧急内存,您必须在链接中包含符号 __ARM_exceptions_buffer_required。 随后,将调用 __ARM_exceptions_buffer_init() 并将其作为异常系统初始化的一部分。 缺省情况下,不包含该符号。

以下例程用于管理异常紧急缓冲区:

extern "C" void *__ARM_exceptions_buffer_init()

在运行时调用一次,以分配紧急缓冲区内存。 它返回指向紧急缓冲区内存的指针;如果未分配内存,则返回 NULL

extern "C" void *__ARM_exceptions_buffer_allocate(void *buffer, size_t size)

将要抛出异常但没有足够可用堆内存来分配异常对象时调用。buffer__ARM_exceptions_buffer_init() 以前返回的值;如果没有调用该例程,则返回 NULL__ARM_exceptions_buffer_allocate() 返回指向在 8 字节边界上对齐的内存 size 个字节的指针;如果无法分配,则返回 NULL

extern "C" void *__ARM_exceptions_buffer_free(void *buffer, void *addr)

调用以释放可能由 __ARM_exceptions_buffer_allocate() 分配的内存。 buffer__ARM_exceptions_buffer_init() 以前返回的值;如果未调用该例程,则返回 NULL。 该例程确定是否已从紧急内存缓冲区中分配了传递的地址;如果已分配,则会适当地将其释放,然后返回非 NULL 值。 如果 __ARM_exceptions_buffer_allocate() 未分配位于 addr 的内存,该例程必须返回 NULL

这些例程的缺省定义存在于映像中,但您可以提供自己的版本以覆盖库提供的缺省定义。 缺省例程只保留足够用于单个 std::bad_alloc 异常对象的空间。 如果不需要紧急缓冲区,则可以安全地将所有这些例程重新定义为只返回 NULL

从 main() 中调用的库函数

main() 函数是应用程序的用户级根。 它要求初始化执行环境,并且要求能够调用输入/输出函数。 在 main() 中,程序可以执行以下操作之一(用于在 C 库中调用用户可自定义的函数):

Copyright © 2007 ARM Limited. All rights reserved. ARM DUI 0349AC
Non-Confidential