_cinit
在完成了_setargv() 以及_setenvp() 之后,进入到_cinit 函数。该函数的注释很短,就一句“do C data initialize”,让人完全摸不着头脑。不过不用着急,可以阅读_cinit 函数的实现来加以分析。
_cinit 函数很短,大致上分为三个步骤:
1. _fpmath() 或者 (*_FPinit)();
2. _initterm( __xi_a, __xi_z );
3. _initterm( __xc_a, __xc_z );
第一步
是可选的,_FPinit 主要用来初始化浮点运算。只有当用户写的代码中出现了浮点运算,_FPinit 才会被定义。关于_FPinit 由于MSDN 上没相关资料,在此不做深究。
第二步和第三步
是分别对C和C++程序做初始化。_initterm 接受两个指针作为参数,这两个指针中间的内存区域是一张函数指针表。_initterm 会从第一个指针开始,慢慢向后寻找,直到第二个指针结束,中间如果找到了一块内存表示一个函数指针,则执行该函数。
/*
* pointers to initialization sections
*/
extern _PVFV __xi_a[], __xi_z[]; /* C initializers */
extern _PVFV __xc_a[], __xc_z[]; /* C++ initializers */
void _initterm ( _PVFV * pfbegin, _PVFV * pfend )
{
/*
* walk the table of function pointers from the bottom up, until
* the end is encountered. Do not skip the first entry. The initial
* value of pfbegin points to the first valid entry. Do not try to
* execute what pfend points to. Only entries before pfend are valid.
*/
while ( pfbegin < pfend )
{
/*
* if current table entry is non-NULL, call thru it.
*/
if ( *pfbegin != NULL )
(**pfbegin)();
++pfbegin;
}
}
_initterm( )
先来看看第二步、第三步中都做了什么。这里继续沿用一段空的C代码(main函数中没有任何东西)来build成exe,随后运行该exe,并且OD到_cinit 内部。
这里的两个call 分别表示调用了 _initterm( __xi_a, __xi_z ) 和_initterm( __xc_a, __xc_z ) 。对应的有:
__xi_a = 00406008
__xi_z = 00406010
和
__xc_a = 00406000
__xc_z = 00406004
继续跟进可以发现,在00406008 至 00406010之间仅有一个函数指针,指向__initmbctable()函数。
__initmbctable() 在第(4)篇_setargv 中曾经有过介绍,它会创建一个新的
_setargv 中已经调用过了该函数,
并将__mbctype_initialized 被设置为1 ,因此这里
__initmbctable()实际上不会重复创建。
在00406000 至 00406004 之间没有函数指针,实际上什么也不执行。
因此,对于空的C程序:
- _initterm( __xi_a, __xi_z ) ------> 调用__initmbctable() ------> 实际上没做什么
- _initterm( __xc_a, __xc_z ) ------> 不产生调用
现在来换一段C程序:
#include <stdio.h>
#include <math.h>
void main()
{
int a = 5;
double b = sqrt(a);
printf("%f\n",b);
}
跟踪的结果为:
- _initterm( __xi_a, __xi_z ) ------> 先后调用__initstdio
,__initmbctable
- _initterm( __xc_a, __xc_z ) ------> 不产生调用
这里由于调用了stdio标准库,因此为了stdio能够正确的工作,需要进行初始化。暂时没有找到关于initstdio
函数的资料,暂时不作深究。
至于为什么第二个initterm 不会产生函数调用,这是因为第二个initterm 是用于C++ data initializations,所以在C 程序中毫无作为。来看一段简单的C++ 代码:
int foo(){
int a=1;
int b=9;
return a+b;
}
int a = foo();
void main()
{
}
注意这里的全局变量a,C语言里是不会允许这种写法的,在C中全局变量只能用常量进行赋值。准确说C中的全局变量在编译期就需要被确定,链接器会把所有的全局变量都放进PE的data区域。说白了,这些全局变量都是直接写死在PE中的。
但是C++ 中确允许像上面那样动态赋值,原因就在于第二次调用 initterm 时,会调用foo函数为A进行初始化。对于上面的C++代码:
- _initterm( __xi_a, __xi_z ) ------> 调用__initmbctable() ------> 实际上没做什么
- _initterm( __xc_a, __xc_z ) ------> 调用foo()函数
利用_initterm( __xc_a, __xc_z ) 对C++ data 进行 initialization 有点儿复杂,这里不讨论。
另参考:
http://blog.donews.com/x140yu/archive/2005/05/26/399256.aspx
分享到:
相关推荐
cinit 为您的C项目创建文件结构和Makefile。 Makefile默认为c99和clang。 用法 使用make编译程序,并使用以下命令调用它: ./cinit 结构 该命令将创建以下结构: <project>/ ├─ Makefile ├─ src/ │ ├─ ...
初始化完成后,程序调用用户编写的main函数,并在main函数执行完毕后调用_cexit进行清理工作。 在main函数执行的过程中,为了处理可能出现的异常情况,代码使用了Windows特有的结构化异常处理(SEH)机制。try-...
extern uint32_t I2CInit_0( uint32_t I2cMode ,int SpeedMode) ;//I2C0支持快速模式和标准模式 extern uint32_t I2CInit_1( uint32_t I2cMode ); extern uint32_t I2CInit_2( uint32_t I2cMode ); uint8_t I2C_...
5. **主函数`main`**:这是程序的入口点,执行了一系列初始化操作,如初始化HAL库、LCD、I2C、ADC、RTC、TIM以及串口。之后启动定时器、中断和UART接收,并进入无限循环,调用各个处理函数以处理硬件操作。 6. **...
这可以通过在`main()`函数之前添加调用`_copyAPI2RAM_()`来实现。 #### 结论 通过对TMS570LS系列微控制器读写BANK0时遇到的问题进行分析与解决,我们了解到在使用F021 Flash API时,不能直接在程序运行所在的BANK...
`.args`段用于存放程序启动时传递给`main()`函数的参数,如命令行参数`argc`、`argv`和环境变量`envp`等。这些参数对于理解程序如何启动至关重要,尤其是当程序需要根据不同的输入参数执行不同的操作时。`.args`段...
本文将从 DSP CCS 初学调试问题的标题、描述、标签和部分内容中提取相关知识点,并对每个问题进行详细的解释和分析。 问题1: RTDX target application does not match emulation protocol! 在使用 CCS 2.0 的 ...
从宽带响应中减去 IR 响应可得出人眼响应的近似值。 用法 在 user_init 调用中: i2cinit(); TSL2561_init(TSL2561_ADDR_F); 注意:如果您的设备地址引脚连接到 VCC 或 GND,请使用 TSL2561_ADDR_L 或 TSL2561_...
5) 调用用户定义的main函数,注意此时硬件、软件中断尚未启用。 6) 调用BIOS_start启动DSP/BIOS,使能各模块并启动MOD_startup。 7) 进入idle循环,根据TSK管理模块是否启用,执行TSK_idle或直接执行IDL_loop。 ...
近程调用使用`call`指令,适用于在同一内存页内的函数调用,而远程调用则使用`fcall`,适用于跨页调用。这直接影响到程序的性能和内存管理。 中断函数在嵌入式系统中扮演重要角色。`interrupt`关键字用于定义中断...
CC运行时环境是嵌入式系统开发中至关重要的一部分,它涉及到程序在目标硬件上的执行模型、存储器管理和函数调用规范等多个方面。本教程主要探讨了C6000系列处理器的CC运行时环境,包括存储器模型、段的概念、初始化...
函数调用约定是指函数调用过程中的行为标准,包括: 1. 将参数放入寄存器或堆栈。 2. 保存需要的寄存器(如a/b0~9)到堆栈。 3. 执行函数调用。 4. 返回时,调用者恢复堆栈和寄存器状态。 理解CC运行时环境的这些...
2. `main.c`:这是项目的主程序,调用驱动程序,执行具体的读写测试任务。 3. `readme.c`:可能包含项目说明或使用指南。 4. `STARTUP.A51`:这是单片机的启动文件,负责设置堆栈、初始化寄存器等启动时必要的工作。...
该程序启动顺序包括七个步骤:初始化DSP、从.cinit段初始化.bss段、调用BIOS_init初始化DSP/BIOS模块、处理.pinit表、调用用户主程序、调用BIOS_start启动DSP/BIOS、执行空闲循环。 第一步:初始化DSP 在这个步骤...
- 定义端口`port10h`为无符号型变量,并将其写入变量`a`,再从`port10h`读取到变量`b`。 - ```c ioport unsigned port10; /* 定义变量访问I/O端口10h */ int func() { port10 = a; /* 把a写入端口10h */ b = ...
- 调用`_main`函数开始执行。 #### 八、变量和函数命名规则 - **全局变量和函数**: - 在C语言和汇编中都需要声明为全局。 - 在C语言中需要有准确的函数原型说明。 - 在汇编语言中变量和函数名前面需要加一个...
链接过程中,需要考虑运行支持库的包含,例如boot.obj,它包含了初始化运行环境的代码,如设置堆栈、处理初始化表、调用main函数和exit函数。特别地,函数_c_int0是程序的入口点。 **运行支持库**是必不可少的,...
c_int00函数的作用是建立C程序的运行环境,完成环境构建后调用main函数,这就是我们自定义的应用程序开始的地方。 【CMD文件介绍】 在DSP开发中,CMD文件扮演着关键角色。它是由用户编写的,用于指导链接器将...