不进入__main,一样能跑程序,因为__main实现的功能,我们自己也都能实现。至于怎么做,看我在最后转的帖子,看完你就了解了。
所谓C库,就是一堆写好的C代码,比如你上面说的malloc,这些代码有的是封装好的(能调用但看不到源码),有的是没封装的(能调用能看源码)。
__main() 和 main()(转载)
因为我们通常在BOOTLOADER中都已做好了比较细致的初始化工作,包括代码的搬运,所以我们最好别再调用库函数__main(),因为__main()作为ADS集成好的库函数,会对系统进行初始化设置,可能会与我们的初始化发生冲突,故在我们做好初始化后最好别调用__main()。仿真时若调了__main()且没设置entry会报警告,__main()库函数代码不太了解,估计跟ADS初始化有关,库函数__main()要慎用。
当所有的系统初始化工作完成之后,就需要把程序流程转入主应用程序,即呼叫主应用程序。最简单的一种情况是:
IMPORT main
B main
直接从启动代码跳转到应用程序的主函数入口,当然主函数名字可以由用户随便定义。
在ARM ADS环境中,还另外提供了一套系统级的呼叫机制。
IMPORT __main
B __main
__main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到main()。所以说,前者是库函数,后者就是我们自己编写的main()主函数;
因此我们用的B __main其实是执行库函数,然后该库函数再调用我们的main() 函数,因此在单步调试时会看到先要跑一段程序(其实是库函数),然后再单步到我们自己的main函数(这个同时也说明如果有B __main 则就对应必须有main函数,否则编译出错),如果我们用 B main来进入我们的主函数的话,那在单步调试时就看到直接进入到我们自己的main函数了,中间不会看到其他程序;
那么用B __main和用B main 这两这进入我们的main函数方式有什么不同呢?
如果采用前者则会由编译器加入一段"段拷贝"程序,即我们说的从加载域到执行域转化程序;而采用后者就没有这个了,因此如果要进行 "段拷贝"只能自己动手编写程序来实现了,完成段拷贝后就可以进入我们的主函数了,当然这个主函数不一定是叫做main(),可以起个其他好听的名字,这个有别于使用B __main方式;不管采用哪种方式进入我们的程序,都要有一段"段拷贝"程序,跑完了段拷贝后才能可以进入我们主程序了!(顺便提一下:startup.s这个文件并没有所谓的"段拷贝"功能,再看也无益!)
对含有启动程序来说,"执行地址与加载地址相同"不容易实现:
如果执行地址与加载地址相同哪当然不需要做"段拷贝",但是个人理解编译器还会加入"段拷贝"程序(如果用B __main的话),只是因为条件不满足而不执行而已;但是对含有启动程序来说,"执行地址与加载地址相同"就不容易了.因为启动程序是要烧到非易失存储器里,用来在上电执行的,而这个程序必定会有RW段,如果RW放在非易失存储器,如FLASH,那就不好实现RW功能了,因此要给RW移动到能够实现RW功能的存储器,如SRAM等.因此,对含有启动程序来说,"执行地址与加载地址相同"就不容易实现;程序的入口点在C 库中的__main 处,在该点,库代码执行以下操作:
1. 将非零(只读和读写)运行区域从其载入地址复制到运行地址。
2. 清零ZI 区域。
3. 跳转到__rt_entry。 KEIL ARM的帮助文件里对__main的描述:
程序的入口点在 C 库中的__main 处,在该点,库代码执行以下操作:
将非根运行区(只读和读写)从其载入地址复制到运行地址。同如果任何区被压缩,将它们从载入地址解压到运行地址。更多信息,请参阅链接器用户指南 。
清零 ZI 区域。
跳转到 __rt_entry。
如果不希望库执行这样的操作,可以如 例 2.1 中所示定义跳转到 __rt_entry的自有__main。
例 2.1. __main 和 __rt_entry
IMPORT __rt_entry
EXPORT __main
ENTRY
__main
B __rt_entry
END
库函数__rt_entry() 运行程序步骤如下:
调用 __rt_stackheap_init() 建立栈和堆。
调用 __rt_lib_init() 初始化引用的库函数、初始化语言环境 (locale),如果必要,还将为main()函数建立argc 和 argv。对于 C++,为任何顶级对象调用构造函数。
对于C++,为任何顶级对象调用构造函数作为 __cpp_initialize__aeabi_。更多信息,请参阅 C++ 初始化,建立和销毁 。
调用 main()函数 ———— 应用程序的用户级根。
从main()函数中,应用程序除了调用其他函数,还可调用库函数。有关详细信息,请参阅从 main()函数中调用库函数。从main()函数中调用库函数。
使用main()函数返回的值调用exit()。
从上面就可以看到__user_initial_stackheap的返回值到底是在哪里用到了,
就是“调用 __rt_stackheap_init() 建立栈和堆”这里,实际上还有其它的C库函数有用到,但肯定都是在__main之后,因为__main是C库函数的入口。
我说上面的代码没有调用__user_initial_stackheap,因为很明显,把STARTUP.S从头读到尾,根本没有调用__user_initial_stackheap的语句。那它在哪呢?“rt_misc.h”这个文件里有定义。
/* * This can be defined to override the standard memory models' way * of determining where to put the initial stack and heap. * * The input parameters R0 and R2 contain nothing useful. The input * parameters SP and SL are the values that were in SP and SL when * the program began execution (so you can return them if you want * to keep that stack). * * The two `limit' fields in the return structure are ignored if * you are using the one-region memory model: the memory region is * taken to be all the space between heap_base and stack_base. */ struct __initial_stackheap { unsigned heap_base; /* low-address end of initial heap */ unsigned stack_base; /* high-address end of initial stack */ unsigned heap_limit; /* high-address end of initial heap */ unsigned stack_limit; /* low-address end of initial stack */ }; extern __value_in_regs struct __initial_stackheap __user_initial_stackheap(unsigned /*R0*/, unsigned /*SP*/, unsigned /*R2*/, unsigned /*SL*/);
由于ARM应用的灵活性,可以通过分散加载文件来定义代码和变量的位置,所以堆栈的地址并不固定,这样,在ARM C库里的函数用到堆栈的地址时候,就用上面定义的变量来代替。
说它是不是多余,其实也不算,因为一般的STARTUP.S都只初始了栈,就是SP。没有初始堆,所以在这个函数里初始堆还是有必要的,而且这个函数的作用主要是返回堆栈的地址,这个地址除了初始化,还可以有其它的作用。毕竟我们不大方便在C语言里直接取SP的地址。
如果没有 B __mian 的话,就没有调用C库,也就用不到初始化C库。而不调用__main,直接进入main()当然是可以实现的,具体方法网上也有,你可以查一下。