C语言进阶(基于ESP-IDF)

主要参考资料:
王利涛《嵌入式C语言自我修养:从芯片、编译器到操作系统》
#pragma的常用方法讲解: https://blog.csdn.net/weixin_39640298/article/details/84503428
C语言再学习 – __attribute__详解: https://blog.csdn.net/qq_29350001/article/details/129390465

C语言标准的发展过程

C语言标准大概经历了下面几个阶段:

  • K&R C
  • ANSI C
  • C99
  • C11

1.K&R C

K&R C一般也称为传统C。在C语言标准没有统一之前,C语言的作者Dennis M.Ritchie和Brian W.Kernighan合作写了一本书《C程序设计语言》。早期程序员编程,这本书可以说是绝对权威的。这本书很薄,内容精炼,主要介绍了C语言的基本编程语法。
后来《C程序设计语言》第二版问世,做了一些修改,如新增unsigned int、long int、struct等数据类型;把运算符=+/=-修改为+=/-=,避免运算符带来的一些歧义和bug。
第二版可以看作ANSI标准的雏形,但早期的C语言还是很简单的,如还没有定义标准库函数、没有预处理命令等。

2.ANSI C

ANSI C是ANSI在K&R C的基础上,统一了各大编译器厂商的不同标准,并对C语言的语法和特性做了一些扩展,在1989年发布的一个标准。这个标准一般也叫作C89/C90标准,也是目前各种编译器默认支持的C语言标准。ANSI C标准主要新增了以下特性。
● 增加了signed、volatile、const关键字。
● 增加了void*数据类型。
● 增加了预处理器命令。
● 增加了宽字符、宽字符串。
● 定义了C标准库。
● ……

3.C99标准

C99标准是ANSI在1999年基于C89标准发布的一个新标准。该标准对ANSI C标准做了一些扩充,如新增了一些关键字,支持新的数据类型。
● 布尔型:_Bool。
● 复数:_Complex。
● 虚数:_Imaginary。
● 内联:inline。
● 指针修饰符:restrict。
● 支持long long、long double数据类型。
● 支持变长数组。
● 允许对结构体特定成员赋值。
● 支持十六进制浮点数、float_Complex等数据类型。
● ……
C99标准也会借鉴其他编程语言的一些优点,对自身的语法和标准做一系列改进,例如:
● 变量声明可以放在代码块的任何地方。ANSI C标准规定变量的声明要全部写在函数语句的最前面,否则就会报编译错误。现在不需要这样写了,哪里需要使用变量,直接在哪里声明即可。
● 源程序每行最大支持4095字节。这个貌似足够用了,没有什么程序能复杂到一行程序有4000多个字符。
● 支持//单行注释。早期的ANSI C标准使用/**/注释,不如C++的//注释方便,所以C99标准就把这种注释吸收过来了,从C99标准开始也支持这种注释方式。
● 标准库新增了一些头文件,如stdbool.h、complex.h、stdarg.h、fenv.h等。大家在C语言中经常返回的true、false,其实这是C++里面的定义的bool类型,早期的C语言是没有bool类型的。那为什么我们经常这样写,而编译器编译程序时没有报错呢?这是因为早期大家编程使用的都是VC++6.0系列,使用的是C++编译器,C++编译器是兼容ANSI C标准的。当然还有一种可能就是有些IDE对这种数据类型做了封装。

4.C11标准

C11标准是ANSI在2011年发布的最新C语言标准,C11标准修改了C语言标准的一些bug,增加了一些新特性。
● 增加_Noreturn,声明函数无返回值。
● 增加_Generic,支持泛型编程。
● 修改了标准库函数的一些bug,如gets()函数被gets_s()函数代替。
● 新增文件锁功能。
● 支持多线程。
● ……
从C11标准的新增内容,我们可以观察到C语言未来的发展趋势。C语言现在也在借鉴现代编程语言的优点,不断添加到自己的标准里。如现代编程语言的多线程、字符串、泛型编程等,C语言最新的标准都支持。但是这样下去,C语言会不会变得越来越臃肿?是不是还能保持它“简单就是美”的初心呢?这一切只能交给时间了,至少目前我们不用担心这些,因为新发布的C11标准,目前绝大多数编译器还不支持,我们暂时还用不到。

编译器对C语言标准的扩展

不同编译器,出于开发环境、硬件平台、性能优化的需要,除了支持C语言标准,还会自己做一些扩展。
在51单片机上用C语言开发程序,我们经常使用Keil for C51集成开发环境。你会发现Keil for C51或者其他IDE里的C编译器会对C语言做很多扩展,如增加了各种关键字。
● data:RAM的低128B空间,单周期直接寻址。
● code:表示程序存储区。
● bit:位变量,常用来定义51单片机的P0~P3管脚。
● sbit:特殊功能位变量。
● sfr:特殊功能寄存器。
● reentrant:重入函数声明。
如果你在程序中使用以上这些关键字,那么你的程序只能使用51编译器来编译运行;
如果你使用其他编译器,如VC++6.0,则编译是通不过的。

同样的道理,GCC编译器也对C语言标准做了很多扩展。
● 零长度数组。
● 语句表达式。
● 内建函数。
● __attribute__特殊属性声明。
● 标号元素。
● case范围。
● ……
如支持零长度数组,这些新增的特性,C语言标准目前是不支持的,其他编译器也不支持。如果你在程序中定义一个零长度数组,则只能使用GCC编译器才能正确编译,使用VC++6.0编译器编译可能就通不过,因为Microsoft的C++编译器不支持这个特性。

在ESP-IDF开发中使用C

ISO C17 的 GNU(–std=gnu17) 是 ESP-IDF 中当前默认的 C 语言版本,编译器为GCC

使用不同的语言标准编译某个组件的源代码,请在组件的 CMakeLists.txt 文件中设置所需的编译器标志:

idf_component_register( ... )
target_compile_options(${COMPONENT_LIB} PRIVATE -std=gnu11)

预处理

预处理器的核心功能在于利用其内建特性对源代码进行预处理,从而实现资源的等价替换。以下是四种常见的预处理操作:

  1. 文件包含:
    #include 指令是预处理中最常用的一种,它允许将一个文件的内容包含到另一个文件中,实现源代码的模块化和重用。

  2. 条件编译:
    通过使用 #if、#ifndef、#ifdef 和 #endif 等指令,预处理器能够在编译时根据条件选择性地包含或排除代码块,这有助于实现版本控制和避免头文件的重复包含。

  3. 布局控制:
    #pragma 指令用于向编译器提供非标准的控制信息,它允许编译器执行特定的操作,如优化代码布局等。

  4. 宏替换:
    #define 指令允许定义宏,这些宏可以代表常量、函数、类型定义或代码片段,从而简化代码的编写和维护。

4.宏替换

在宏定义中使用语句表达式

GNU C对C语言标准作了扩展,允许在一个表达式里内嵌语句,允许在表达式内部使用局部变量、for循环和goto跳转语句。这种类型的表达式,我们称为语句表达式。语句表达式的格式如下。

({表达式1;表达式2;表达式3})

语句表达式最外面使用小括号()括起来,里面一对大括号{}包起来的是代码块,代码块里允许内嵌各种语句。语句的格式可以是一般表达式,也可以是循环、跳转语句。
和一般表达式一样,语句表达式也有自己的值。语句表达式的值为内嵌语句中最后一个表达式的值。我们举个例子,使用语句表达式求值。

__attribute__关键字

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值