表达式

表达式和语句是 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\n",i);
{
int i = 4;
printf("i=%d\n",i);
}
printf("i=%d\n",i);
return 0;
}
运行结果为:
i=3
i=4
i=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\n",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\n",sum);
here:
printf("here:\n");
printf("sum = %d\n",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\n",MAX(int,i++,j++));
printf("max=%f\n",MAX(float,3.14,3.15));
return 0;
}

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

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

语句表达式,作为 GNU C 对 C 标准的一个扩展,在内核中,尤其是在内核的宏定义中,被大量的使用。使用语句表达式定义宏,不仅可以实现复杂的功能,还可以避免宏定义带来的一些歧义和漏洞。比如在 Linux 内核中,max_t 和min_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 开源软件中,你会发现,还有大量的宏定义使用了语句表达式。通过本节教程的学习,相信大家以后再碰到这种使用语句表达式定义的宏,肯定就知道是怎么回事了,心中有丘壑,再也不用慌

Linux高级之语句表达式的更多相关文章

  1. C语言在宏定义中使用语句表达式和预处理器运算符

    语句表达式的亮点在于定义复杂功能的宏.使用语句表达式来定义宏,不仅可以实现复杂的功能,而且还能避免宏定义带来的歧义和漏洞.下面以一个简单的最小值的宏为例子一步步说明. 1.灰常简单的么,使用条件运算符 ...

  2. 嵌入式C语言自我修养 03:宏构造利器:语句表达式

    3.1 基础复习:表达式.语句和代码块 表达式 表达式和语句是 C 语言中的基础概念.什么是表达式呢?表达式就是由一系列操作符和操作数构成的式子.操作符可以是 C 语言标准规定的各种算术运算符.逻辑运 ...

  3. Linux高级知识

    Linux高级知识 十一.LAMP架构 1.LAMP架构介绍.MySQL.MariaDB介绍.MySQL安装 2.MariaDB和Apache安装 3.安装PHP5和PHP7 4.Apache和PHP ...

  4. Linux高级调试与优化——gdb调试命令

    番外 2019年7月26日至27日,公司邀请<软件调试>和<格蠹汇编——软件调试案例集锦>两本书的作者张银奎老师进行<Linux高级调试与优化>培训,有幸聆听张老师 ...

  5. javascript语句——表达式语句、块语句、空语句和声明语句

    × 目录 [1]表达式 [2]块语句 [3]空语句[4]声明 前面的话 如果表达式在javascript中是短语,那么语句(statement)就是javascript整句或命令.表达式计算出一个值, ...

  6. linux高级编程基础系列:线程间通信

    linux高级编程基础系列:线程间通信 转载:原文地址http://blog.163.com/jimking_2010/blog/static/1716015352013102510748824/ 线 ...

  7. T-SQL高级查询语句(父子查询)

    T-SQL高级查询语句 高级查询 1.连接查询,对结果集列的扩展 select * from info select * from info,nation #形成笛卡尔积 select * from ...

  8. [转帖]Linux教程(21)-Linux条件循环语句

    Linux教程(21)-Linux条件循环语句 2018-08-24 16:49:03 钱婷婷 阅读数 60更多 分类专栏: Linux教程与操作 Linux教程与使用   版权声明:本文为博主原创文 ...

  9. Linux高级命令进阶(week1_day2)

    Linux高级命令进阶(week1_day2)--技术流ken   输出重定向   场景:一般命令的输出都会显示在终端中,有些时候需要将一些命令的执行结果想要保存到文件中进行后续的分析/统计,则这时候 ...

随机推荐

  1. canal同步异常:当表结构变化时,同步失败

    场景 canal 同步Mysql一段时间后突然失败,报如如下错误: 2021-08-06 16:16:51.732 [MultiStageCoprocessor-Parser-Twt_instance ...

  2. 《MySQL实战45讲》(1-7)笔记

    <MySQL实战45讲>笔记 目录 <MySQL实战45讲>笔记 第一节: 基础架构:一条SQL查询语句是如何执行的? 连接器 查询缓存 分析器 优化器 执行器 第二节:日志系 ...

  3. C语言复习(六)----typedef 的作用

    typedef的作用 重命名变量:typedef unsigned int Uint;//可以使用Uint代替unsigned int 定义新的数据类型 typedef struct Books{ c ...

  4. Promise/A+规范-翻译

    Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是 then 方法,该方法注册了两个回调函数,用于接收 promise 的终值或本 promise 不能执行的原因. 本规范详细列出了 ...

  5. 百度地图API开发的快速使用和大量坐标点操作【点聚合,海量点,mapv】

    快速上手 注意:本篇文章代码是基于 百度地图 JavaScript API v3.0 的条件下编写,GL版本可能稍有变化. 地图嘛,很重要的一部分就是坐标经纬度了: 经度: 英文 longitude ...

  6. STM32启动文件详解及SystemInit函数分析(转)

    ;先在RAM中分配系统使用的栈,RAM的起始地址为0x2000_0000 ;然后在RAM中分配变量使用的堆 ;然后在CODE区(flash)分配中断向量表,flash的起始地址为0x0800_0000 ...

  7. Linux远程访问控制

    目录 一.SSH远程管理 1.1.定义 1.2.配置OpenSSH服务端 二.sshd服务支持两种验证方式 2.1.密码验证 2.2.使用SSH客户端程序 ssh远程登录 scp远程复制 sftp安全 ...

  8. Asp.Net Core 导入Excel数据到Sqlite数据库并重新导出到Excel

    Asp.Net Core 导入Excel数据到Sqlite数据库并重新导出到Excel 在博文"在Asp.Net Core 使用 Sqlite 数据库"中创建了ASP.NET Co ...

  9. kali 免杀工具shellter安装以及使用

    Shellter 是一款动态 shellcode 注入工具,我们可以将shellcode注入到其它程序上,从而来躲避杀毒软件的查杀.俗称为免杀 官网:https://www.shellterproje ...

  10. Docker搭建网页数据库管理-Adminer

    为什么Adminer比phpMyAdmin更好? 官网介绍: 用Adminer替换phpMyAdmin,您将获得一个简洁的用户界面,对MySQL功能的更好支持,更高的性能和更高的安全性.请参阅详细比较 ...