1.名字与内存位置之间的关联并不是硬件所提供的,它是由编译器为我们实现的。所有这些变量给了我们一种更方便的方法记住地址----硬件仍然通过地址访问内存位置。----------《c和指针》中的一句话。 对于编译器,它会搜集我们的变量名,比如我们定义了一个全局的int a;那么编译器都为我们做了什么呢? 它会为程序预留4个字节的空间(假设在32位平台),并把我们的变量名“a”保存进符号表,并用这个符号表的索引对应实际的空间。变量名不是内存地址。
2.条件编译
#define JUDGE 1
#if JUDGE
#define parameter 123
#else
#define parameter 321
#endif
条件编译:parameter在不同的情况下需要定义不同的值,当JUDGE为1时,定义parameter为123;当JUDGE为0时,定义parameter为321
3.关键字volatile
volatile本意为“易变的”。在嵌入式环境中用volatile关键字声明的变量,在每次对其值进行引用的时候都会从原始地址取值,而不会将值保存在栈或其他位置。由于该值“易变”的特性所以,针对其的任何赋值或者获取值操作都会被执行(而不会被优化)。由于这个特性,所以该关键字在嵌入式编译环境中经常用来消除compiler的优化。CPU跑指令去拿数据的时候,正常来说都是通过地址总线去寻址,然后直接从数据总线上拿到对应的数据,拿完之后会放在CPU内部用来存放数据的一些小型存储区域----即寄存器。如果有个变量一直在改,编译器优化后,改完了就放在CPU的寄存器,后面再用到,还是直接从寄存器里面拿,不直接再次访问内存的对应地方了,这样效率就高了。volatile就是告诉编译器每次要用这个变量的时候,不要优化上面的步骤,每次我都从内存对应地方去拿,为什么呢?因为有可能那部分内存是其他地方可能也会改的,如果你还是每次直接从寄存器里面拿,就拿不到最新的数据了。(转自知乎)
4.C库函数重定向:用户能定义自己的C语言库函数,连接器在连接时自动使用这些新的功能函数。这个过程叫做重定向C语言库函数,举例来说,用户有一个I/O设备(如UART)。本来库函数fputc()是把字符输出到调试器控制窗口中去的,但用户把输出设备改成了UART端口,这样一来,所有基于fputc()函数的printf()系列函数输出都被重定向到UART端口上去了。
5.断言“assert_param”实际是一个宏,在库函数中它用于检查输入参数是否符合要求,若不符合要求则执行某个函数输出警告,
assert.h头文件支持的断言库是一个用于辅助调试程序的小型库。它由assert()宏组成,接受一个整型表达式作为参数。如果表达式求值为假(非零),assert()宏就在标准错误流(stderr)中写入一条错误信息,并调用abort()函数终止程序(abort()函数的原型在stdlib.h头文件中)。assert()宏是为了标识出程序中某些条件为真的关键位置,如果其中的一个具体条件为假,就用assert()语句终止程序。通常,assert()的参数是一个条件表达式或逻辑表达式。如果assert()中止了程序,它首先会显示失败的测试、包含测试的文件名和行号。
#ifdef USE_FULL_ASSERT
/**
* @brief The assert_param macro is used for function's parameters check.
* @param expr: If expr is false, it calls assert_failed function which reports
* the name of the source file and the source line number of the call
* that failed. If expr is true, it returns no value.
* @retval None
*/
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
void assert_failed(uint8_t* file, uint32_t line);
#else
#define assert_param(expr) ((void)0)
#endif /* USE_FULL_ASSERT */