1 Linux内核中C语法扩展-语句表达式-德赢Vwin官网 网
0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux内核中C语法扩展-语句表达式

汽车电子技术 来源:宅学部落 作者:王利涛 2023-02-17 09:30 次阅读

基础复习:表达式、语句和代码块

表达式

表达式和语句是 C 语言中的基础概念。什么是表达式呢?表达式就是由一系列操作符和操作数构成的式子。操作符可以是 C 语言标准规定的各种算术运算符、逻辑运算符、赋值运算符、比较运算符等。操作数可以是一个常量,也可以是一个变量。表达式也可以没有操作符,单独的一个常量甚至是一个字符串,也是一个表达式。下面的字符序列都是表达式:

  • 2 + 3
  • 2
  • i = 2 + 3
  • i = i++ + 3
  • "wit"

表达式一般用来数据计算或实现某种功能的算法。表达式有2个基本属性:值和类型。如上面的表达式2+3,它的值为5。根据操作符的不同,表达式可以分为多种类型,如:

  • 关系表达式
  • 逻辑表达式
  • 条件表达式
  • 赋值表达式
  • 算术表达式
  • ……

语句

语句是构成程序的基本单元,一般形式如下:

表达式 ;
i = 2 + 3 ;

表达式的后面加一个; 就构成了一条基本的语句。编译器在编译程序、解析程序时,不是根据物理行,而是根据分号 ; 来判断一条语句的结束标记的。如 i = 2 + 3; 这条语句,你写成下面的形式也是可以编译通过的:

i =
2 + 
3
;

代码块

不同的语句,使用大括号{}括起来,就构成了一个代码块。C 语言允许在代码块里定义一个变量,这个变量的作用域也仅限于这个代码块内,因为编译器就是根据{}来做入栈出栈操作来管理变量的作用域的。如下面的程序:

int main(void)
{
    int i = 3;
    printf("i=%d
",i);
    {
        int i = 4;
        printf("i=%d
",i);
    }
    printf("i=%d
",i);
    return 0;
}
运行结果为:
i=3i=4i=3

语句表达式

语句表达式的定义

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

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

语句表达式最外面使用小括号()括起来,里面一对大括号{}包起来的是代码块,代码块里允许内嵌各种语句。语句的格式可以是 “表达式;”这种一般格式的语句,也可以是循环、跳转等语句。

跟一般表达式一样,语句表达式也有自己的值。语句表达式的值为内嵌语句中最后一个表达式的值。我们举个例子,使用语句表达式求值。

int main(void)
{
  int sum = 0;
  sum =
  ({
    int s = 0;
    for( int i = 0; i < 10; i++)
      s = s + i;
      s;
  });
  printf("sum = %d
",sum);
  return 0;
}

在上面的程序中,通过语句表达式实现了从1到10的累加求和,因为语句表达式的值等于最后一个表达式的值,所以在 for 循环的后面,我们要添加一个 s; 语句表示整个语句表达式的值。如果不加这一句,你会发现 sum=0。或者你将这一行语句改为100; 你会发现最后 sum 的值就变成了100,这是因为语句表达式的值总等于最后一个表达式的值。

语句表达式内使用 goto 跳转

在上面的程序中,我们在语句表达式内定义了局部变量,使用了 for 循环语句。在语句表达式内,我们同样也可以使用 goto 进行跳转。

int main(void)
{
  int sum = 0;
  sum = ({
    int s = 0;     
    for( int i = 0; i < 10; i++)
       s = s + i;        
       goto here;
       s;  
    });
  printf("sum = %d
",sum);
  here:
    printf("here:
");
    printf("sum = %d
",sum);
  return 0;
}

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

语句表达式的亮点在于定义复杂功能的宏。使用语句表达式来定义宏,不仅可以实现复杂的功能,而且还能避免宏定义带来的歧义和漏洞。下面就以一个宏定义例子,让我们来见识见识语句表达式在宏定义中的强悍杀伤力!

假如你此刻正在面试,面试职位是:Linux C 语言开发工程师。面试官给你出了一道题:

请定义一个宏,求两个数的最大值。

别看这么简单的一个考题,面试官就能根据你写出的宏,来判断你的 C 语言功底,来决定给不给你 Offer。

合格

对于学过 C 语言的同学,写出这个宏基本上不是什么难事,使用条件运算符就能完成:

#define  MAX(x,y)  x > y ? x : y

这是最基本的 C 语言语法,如果连这个也写不出来,估计场面会比较尴尬。面试官为了缓解尴尬,一般会对你说:小伙子,你很棒,回去等消息吧,有消息,我们会通知你!这时候,你应该明白:不用再等了,赶紧把这篇文章看完,接着面下家。这个宏能写出来,也不要觉得你很牛 X,因为这只能说明你有了 C 语言的基础,但还有很大的进步空间。比如,我们写一个程序,验证一下我们定义的宏是否正确:

#define MAX(x,y) x > y ? x : y
int main(void)
{
  printf("max=%d",MAX(1,2));
  printf("max=%d",MAX(2,1));
  printf("max=%d",MAX(2,2));
  printf("max=%d",MAX(1!=1,1!=2));
  return 0;
}

测试程序吗,我们肯定要把各种可能出现的情况都测一遍。这不,测试第4行语句,当宏的参数是一个表达式,发现实际运行结果为 max=0,跟我们预期结果 max=1 不一样。这是因为,宏展开后,就变成了这个样子:

printf("max=%d",1!=1>1!=2?1!=1:1!=2);

因为比较运算符 > 的优先级为6,大于 !=(优先级为7),所以展开的表达式,运算顺序发生了改变,结果就跟我们的预期不一样了。为了避免这种展开错误,我们可以给宏的参数加一个小括号()来防止展开后,表达式的运算顺序发生变化。这样的宏才能算一个合格的宏:

#define MAX(x,y) (x) > (y) ? (x) : (y)

中等

上面的宏,只能算合格,但还是存在漏洞。比如,我们使用下面的代码测试:

#define MAX(x,y) (x) > (y) ? (x) : (y)
int main(void)
{
  printf("max=%d",3 + MAX(1,2));
  return 0;
}

在程序中,我们打印表达式 3 + MAX(1, 2) 的值,预期结果应该是5,但实际运行结果却是1。我们展开后,发现同样有问题:

3 + (1) > (2) ? (1) : (2);

因为运算符 + 的优先级大于比较运算符 >,所以这个表达式就变为4>2?1:2,最后结果为1也就见怪不怪了。此时我们应该继续修改这个宏:

#define MAX(x,y) ((x) > (y) ? (x) : (y))

使用小括号将宏定义包起来,这样就避免了当一个表达式同时含有宏定义和其它高优先级运算符时,破坏整个表达式的运算顺序。如果你能写到这一步,说明你比前面那个面试合格的同学强,前面那个同学已经回去等消息了,我们接着面试下一轮。

良好

上面的宏,虽然解决了运算符优先级带来的问题,但是仍存在一定的漏洞。比如,我们使用下面的测试程序来测试我们定义的宏:

#define MAX(x,y) ((x) > (y) ? (x) : (y))
int main(void)
{    
  int i = 2;
  int j = 6;
  printf("max=%d",  MAX(i++,j++));
  return 0;
}

在程序中,我们定义两个变量 i 和 j,然后比较两个变量的大小,并作自增运算。实际运行结果发现 max = 7,而不是预期结果 max = 6。这是因为变量 i 和 j 在宏展开后,做了两次自增运算,导致打印出 i 的值为7。

遇到这种情况,那该怎么办呢? 这时候,语句表达式就该上场了。我们可以使用语句表达式来定义这个宏,在语句表达式中定义两个临时变量,分别来暂储 i 和 j 的值,然后进行比较,这样就避免了两次自增、自减问题。

#define MAX(x,y)({  
    int _x = x;       int _y = y;       _x > _y ? _x : _y; })
int main(void)
{
    int i = 2;
    int j = 6;
    printf("max=%d",    MAX(i++,j++));  
    return 0;
}

在语句表达式中,我们定义了2个局部变量 _x_y 来存储宏参数 x 和 y 的值,然后使用 _x 和 _y 来比较大小,这样就避免了 i 和 j 带来的2次自增运算问题。

你能坚持到了这一关,并写出这样自带 BGM 的宏,面试官心里可能已经有了给你 Offer 的意愿了。但此时此刻,千万不要骄傲!为了彻底打消面试官的心理顾虑,我们需要对这个宏继续优化。

优秀

在上面这个宏中,我们定义的两个临时变量数据类型是 int 型,只能比较两个整型的数据。那对于其它类型的数据,就需要重新再定义一个宏了,这样太麻烦了!我们可以基于上面的宏继续修改,让它可以支持任意类型的数据比较大小:

#define MAX(type,x,y)({  
    type _x = x;      type _y = y;      _x > _y ? _x : _y; })

int main(void)
{
    int i = 2;
    int j = 6;
    printf("max=%d
",    MAX(int,i++,j++));
    printf("max=%f
",    MAX(float,3.14,3.15));
    return 0;
}

在这个宏中,我们添加一个参数:type,用来指定临时变量 _x 和 _y 的类型。这样,我们在比较两个数的大小时,只要将2个数据的类型作为参数传给宏,就可以比较任意类型的数据了。如果你能在面试中,写出这样的宏,面试官肯定会非常高兴,他一般会跟你说:稍等,待会 HR 会跟你谈待遇问题。恭喜你,你拿到 Offer 了!

语句表达式在 Linux 内核中的使用

语句表达式,作为 GNU C 对 C 标准的一个扩展,在内核中,尤其是在内核的宏定义中,被大量的使用。使用语句表达式定义宏,不仅可以实现复杂的功能,还可以避免宏定义带来的一些歧义和漏洞。比如在 Linux 内核中,max_tmin_t 的宏定义,就使用了语句表达式:

#define min_t(type, x, y) ({
    type __min1 = (x); 
    type __min2 = (y); 
    __min1 < __min2 ? __min1 : __min2; })
#define max_t(type, x, y) ({ 
    type __max1 = (x); 
    type __max2 = (y); 
    __max1 > __max2 ? __max1 : __max2; })

除此之外,在 Linux 内核、GNU 开源软件中,你会发现,还有大量的宏定义使用了语句表达式。通过本节教程的学习,相信大家以后再碰到这种使用语句表达式定义的宏,肯定就知道是怎么回事了,心中有丘壑,再也不用慌。

备注

本教程是《C语言嵌入式Linux高级编程》第5期:Linux内核中的GNU C语法扩展,文本预览版,如果想系统学习Linux内核中的各种GNU C扩展及使用技巧,可百度搜索:“王利涛”,到51CTO学院或CSDN学院点击相关课程即可开始系统学习。

如果您手头暂时没有 Linux 学习环境,也可以在 Windows 环境下安装 C-Free 学习。教程中的 C 语言示例程序在 C-Free 环境下面也能编译通过。当然在这里,还是建议您使用虚拟机安装一个 Linux 学习环境,一个良好的环境更有利于我们的学习,在安装过程有什么疑惑,可以通过邮件(3284757626@qq.com)联系,也可以加入QQ群(475504428),参与技术讨论。

微信公众号:宅学部落

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表德赢Vwin官网 网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11292

    浏览量

    209309
  • C语言
    +关注

    关注

    180

    文章

    7604

    浏览量

    136678
  • LINUX内核
    +关注

    关注

    1

    文章

    316

    浏览量

    21644
  • 运算符
    +关注

    关注

    0

    文章

    172

    浏览量

    11078
  • 操作符
    +关注

    关注

    0

    文章

    21

    浏览量

    9040
收藏 人收藏

    评论

    相关推荐

    什么是正则表达式?正则表达式如何工作?哪些语法规则适用正则表达式

    正则表达式又称规则表达式(Regular Expression,在代码中常简写为 regex、regexp 或 RE),是一种用于匹配、查找、替换文本的强大工具。它能够以特定的模式匹配字符串,从而
    的头像 发表于 11-03 14:41 3525次阅读
    什么是正则<b class='flag-5'>表达式</b>?正则<b class='flag-5'>表达式</b>如何工作?哪些<b class='flag-5'>语法</b>规则适用正则<b class='flag-5'>表达式</b>?

    【跟我学单片机C语言】全面分析C51表达式语句及仿真器

    在前面的课程的例子里,也简单理解过一些语句的使用方法,能看出C语言是一种结构化的程序设计语言。C 语言供给了相当丰富的程序控制语句。学习掌
    发表于 12-12 10:27

    总结linux脚本语法和正则表达式的应用

    每日学一点之linux脚本语法以及正则表达式基础
    发表于 11-08 09:23

    Linux内核C语言语法扩展

    收集一些感觉比较好的博客链接,方便以后再看Linux 内核C 语言语法扩展
    发表于 12-15 06:04

    防范表达式的失控

    C 语言中,表达式是最重要的组成部分之一,几乎所有的代码都由表达式构成。表达式的使用如此广泛,读者也许会产生这样的疑问,像+ 、- 、3 、/ 、& & 这样简单的运算也会出现
    发表于 04-22 16:57 13次下载

    C语言程序设计--运算符与表达式

    c语言对数据有很强的表达能力,具有十分丰富的运算符,利用这些运算符可以组成各种表达式语句。运算符就是完成某种特定运算的符号表达式则是由运算
    发表于 07-14 21:30 46次下载

    第十课 C51表达式语句及仿真器

    丰富的程序控制语句。学习掌握这些语句的使用方法也是单片机C语言学习的重点。 表达式语句是最基本
    发表于 11-22 12:45 606次阅读
    第十课 <b class='flag-5'>C</b>51<b class='flag-5'>表达式</b><b class='flag-5'>语句</b>及仿真器

    51单片机表达式语句的用法解析

    以上的都是合法的表达式语句。在我收到的一些网友的Email,发现很多初学的朋友往往在编写调试程序时忽略了分号“;”,造成程序不法被正常的编译。我个人的经验是在遇到编译错误时先语法是否
    发表于 04-15 15:17 3710次阅读
    51单片机<b class='flag-5'>表达式</b><b class='flag-5'>语句</b>的用法解析

    Python正则表达式指南

    本文介绍了Python对于正则表达式的支持,包括正则表达式基础以及Python正则表达式标准库的完整介绍及使用示例。本文的内容不包括如何编写高效的正则表达式、如何优化正则
    发表于 03-26 09:13 10次下载
    Python正则<b class='flag-5'>表达式</b>指南

    C语言如何使用正则表达式

    C语言的正则表达式规则,特此跟大家分享。
    的头像 发表于 03-16 08:41 4932次阅读

    Lambda表达式详解

    C++11的Lambda表达式用于 **定义并创建匿名的函数对象** ,以简化编程工作。下面看一下Lambda表达式的基本构成。
    的头像 发表于 02-09 11:28 1158次阅读

    C语言的表达式

    C语言中,表达式是由操作符和操作数组成。表达式可以由一个或者多个操作数组成,不同的操作符与操作数组成不同的表达式,因此,表达式才是
    的头像 发表于 02-21 15:09 1348次阅读
    <b class='flag-5'>C</b>语言的<b class='flag-5'>表达式</b>

    shell脚本基础:正则表达式grep

    Linux上有许多命令可以使用正则表达式,其中最常见的是grep命令和sed命令。正则表达式有多种类型,每种类型可以使用的元字符类型不同。最常见的是基本正则
    的头像 发表于 05-29 09:29 1249次阅读
    shell脚本基础:正则<b class='flag-5'>表达式</b>grep

    Python的正则表达式

    字符串的特征,这样我们就可以快速、高效地处理大量的文本数据。 基础语法 在Python,使用re模块来操作正则表达式。re模块提供了很多函数,用于对字符串进行正则匹配和替换。以下是一些常用的正则
    的头像 发表于 06-21 16:52 982次阅读

    linux正则表达式匹配字符串

    Linux操作系统,正则表达式是一种强大的模式匹配工具,它可以用于在文本查找、替换和筛选特定的字符串。准确掌握正则表达式
    的头像 发表于 11-23 10:26 721次阅读