【C++】 内联函数详解(搞清内联的本质及用法)

article/2025/9/17 3:46:19

目录

一.什么是内联函数

1.直观上定义:

2.更深入的思考:

二.为什么使用内联函数

1.为什么要代替部分宏定义

2.普通函数频繁调用的过程消耗栈空间

3.更深入的思考

三.内联函数和编译过程的相爱相杀

四.内联函数怎么用,在哪儿用?

五.内联函数和重定义

六.内联函数的类方法实现

五.内联与宏

六.内联的局限性

七.内联的使用建议

八.内联和static


一.什么是内联函数

1.直观上定义:

联函数的定义与普通函数基本相同,只是在函数定义前加上关键字 inline。

inline void print(char *s)
{printf("%s", s);
}

2.更深入的思考:

  • 函数前面加上inline一定会有效果吗?
  • 如果不加inline就不是内联函数了吗?

后面让我们慢慢来解答这两个问题 内联函数和编译过程的相爱相杀

二.为什么使用内联函数

  • 内联函数最初的目的:代替部分 #define 宏定义
  • 使用内联函数替代普通函数的目的:提高程序的运行效率;

针对上述两个方面我们展开讨论:

1.为什么要代替部分宏定义

  • ​​宏是预处理指令,在预处理的时候把所有的宏名用宏体来替换;内联函数是函数,在编译阶段把所有调用内联函数的地方把内联函数插入;
  • 宏没有类型检查,无论对还是错都是直接替换;而内联函数在编译时进行安全检查;​
  • 宏的编写有很多限制,例如只能写一行,不能使用return控制流程等;
  • 对于C++ 而言,使用宏代码还有另一种缺点:无法操作类的私有数据成员。

2.普通函数频繁调用的过程消耗栈空间

函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码。CPU 在执行主调函数代码时如果遇到了被调函数,主调函数就会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回到主调函数,主调函数根据刚才的状态继续往下执行。

一个 C/C++程序的执行过程可以认为是多个函数之间的相互调用过程,它们形成了一个或简单或复杂的调用链条,这个链条的起点是main(),终点也是main()。当main()调用完了所有的函数,它会返回一个值(例如return 0;)来结束自己的生命,从而结束整个程序。

函数调用是有时间和空间开销的。程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。

栈空间就是指放置程式的局部数据也就是函数内数据的内存空间,在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足所造成的程式出错的问题,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。

如果函数体代码比较多,需要较长的执行时间,那么函数调用机制占用的时间可以忽略;如果函数只有一两条语句,那么大部分的时间都会花费在函数调用机制上,这种时间开销就就不容忽视。

具体的一个调用效率讨论在第六章节 内联函数的类方法实现

为了消除函数调用的时空开销,C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数(Inline Function)。但也存在缺点,就是每一调用处均会展开,增加了重复的代码量。

可以理解为内联函数的关键词是:替换

3.更深入的思考

通过上述内容我们知道内联函数是在调用的地方展开函数定义,那么问题又来了,展开也好,替换也好,都存在下面两个问题:

  • 内联函数一定就会展开吗?
  • 在什么情况下内联函数会展开?

三.内联函数和编译过程的相爱相杀

在这一节,我们先一口气回答前两节的所有问题,然后慢慢引出后面的话题。

  • 函数前面加上inline一定会有效果吗?

答:不会,使用内联inline关键字修饰函数只是一种提示,编译器不一定认。

  • 如果不加inline就不是内联函数了吗?

答:存在隐式内联,不用inline关键字,C++中在类内定义的所有函数都自动称为内联函数。

  • 内联函数一定就会展开吗?

答:其实和第一个问题类似,还是看编译器认不认。

  • 在什么情况下内联函数会展开?

答:首先需要满足有inline修饰或者是类中的定义的函数,然后再由编译器决定。


其实说白了,内联函数管不管用是由编译器说了算的!

那如何要求编译器展开内联函数呢?

  1. 编译器开优化:gcc -O2 test.c -o test,只有在编译器开启优化选项的时候,才会有inline行为的存在,比如对g++在-O0时就不会作任何的inline处理,对于-O2的优化方式,编译器会通过启发式算法决定是否值得对一个函数进行内联,同时要保证不会对生成文件的大小产生较大影响。 而-O3模式则不在考虑生成文件的大小;
  2. 使用attribute属性:static inline __attribute__((always_inline)) int add_i(int a,int b);
  3. 使用auto_inline:#pragma auto_inline(on/off),当使用#pragma auto_inline(off)指令时,会关闭对函数的inline处理,这时即使在函数前面加了inline指令,也不会对函数进行内联处理。

上述操作都仅仅是对编译器提出内联的建议,最终是否进行内联由编译器自己决定,大多数编译器拒绝它们认为太复杂的内联函数(例如,那些包含循环或者递归的),而且类的构造函数、析构函数和虚函数往往不是内联函数的最佳选择。

有关visual studio中编译优化选择的位置如图,gcc编译见上面的例子也可以直接man gcc查看。

四.内联函数怎么用,在哪儿用?

基本介绍完内联的概念,接下来说说内联怎么用,在哪儿用?

内联函数是定义在头文件还是源文件?

内联展开是在编译时进行的,只有链接的时候源文件之间才有关系。所以内联要想跨源文件必须把实现写在头文件里。如果一个内联函数会在多个源文件中被用到,那么必须把它定义在头文件中

内联函数的定义不一定要跟声明放在一个头文件里面:定义可以放在一个单独的头文件中,里面需要给函数定义前加上inline 关键字,原因看下面第 2.点;然后声明 放在另一个头文件中,此文件include上一个头文件。这种用法 boost里很常见:优点1. 实现跟API分离封装。优点2. 可以解决有关inline函数的循环调用问题。

1.隐式内联:如第三节说的C++中在类内定义的所有函数都自动称为内联函数,类的成员函数的定义直接写在类的声明中时,不需要inline关键字

#include <stdio.h>class Trace{
public:Trace(){noisy = 0;}void print(char *s){if (noisy){printf("%s", s);}}void on(){ noisy = 1; }void off(){ noisy = 0; }
private:int noisy;
};

2.显示内联:需要使用inline关键字

#include <stdio.h>class Trace{
public:Trace(){noisy = 0;}void print(char *s); //类内没有显示声明void on(){ noisy = 1; }void off(){ noisy = 0; }
private:int noisy;
};
//类外显示定义
inline void Trace::print(char *s)
{if (noisy){printf("%s", s);}
}

五.内联函数和重定义

这一部分我们带着问题一步步进行分析思考


  • 什么是重定义?

答:C/C++语法中,如果变量、函数在同一个工程中被多次定义,链接期间会报类似“对 xxx 多重定义”的错误。

当内联函数的声明和定义分别在头文件和源文件中,并且在其他文件中被调用时,链接期间编译器会报“对 xxx 未定义的引用”错误。内联函数如果会在多处被调用,则需要将函数的定义写在头文件中。

  • ​为什么inline关键字修饰的函数定义在头文件中(函数可能会被多次定义),编译器不会报“对 xxx 多重定义”的错误呢?

答:编译器对被inline修饰的函数做了特殊处理,inline起到了内联的作用;

  • inline为什么能起作用?

答:因为inline是一个弱符号;

  • 什么是弱符号?

答:在C语言中,编译器默认函数和初始化了的全局变量为强符号(Strong Symbol),未初始化的全局变量为弱符号(Weak Symbol)。强符号之所以强,是因为它们拥有确切的数据,变量有值,函数有函数体;弱符号之所以弱,是因为它们还未被初始化,没有确切的数据。

链接器会按照如下的规则处理被多次定义的强符号和弱符号:
1) 不允许强符号被多次定义,也即不同的目标文件中不能有同名的强符号;如果有多个强符号,那么链接器会报符号重复定义错误。

2) 如果一个符号在某个目标文件中是强符号,在其他文件中是弱符号,那么选择强符号。

3) 如果一个符号在所有的目标文件中都是弱符号,那么选择其中占用空间最大的一个。

  • 怎么知道inline是个弱符号的?

答:反汇编:objdump -dS test,看下面两个例子:

下面两个.cpp文件,除Function()的inline修饰符外其他内容完全一致

1.正常的函数

/* NormalMain.cpp */
#include <cstdio>void Function()
{printf("[Function]========= Get!!!\n");
}int main() {Function();
}

汇编结果 :

	.file	"NormalMain.cpp".section	.rodata
.LC0:.string	"[Function]========= Get!!!".text.globl	_Z8Functionv.type	_Z8Functionv, @function
_Z8Functionv:
;...

 2.内联函数

/* InlineMain.cpp */
#include <cstdio>inline void Function()
{printf("[Function]========= Get!!!\n");
}int main() {Function();
}

 汇编结果 :

	.file	"InlineMain.cpp".section	.rodata
.LC0:.string	"[Function]========= Get!!!".section	.text._Z8Functionv,"axG",@progbits,_Z8Functionv,comdat.weak	_Z8Functionv.type	_Z8Functionv, @function
_Z8Functionv:
;...

很容易发现,inline修饰的Function()编译结果是.weak弱符号,而没有被inline修饰的Function()编译结果是.globl全局符号(强符号) 因此,如果一个符号在所有的目标文件中都是弱符号,那么选择其中占用空间最大的一个,那么问题又来了:

  • 如果 1.obj 和 2.obj 中同名的 inline 函数体,它们的实现代码不一样会如何?

答:按理说选择其中占用空间最大的一个,但是链接器选了最偷懒的方案,会挑最先碰到的那个来用,甚至不会给出警告。

所以内联函数里面坑很多,即使因为inline的弱符号特性避免了重定义,但是也会出现其他坑,比如编译器优化开关关闭时,inline函数可能会被同名的其它函数替代,弱符号被强符号代替,从而影响业务逻辑的正确性。

/* InlineMain.cpp */
#include <cstdio>inline void Function()
{printf("[Function]========= Get!!!\n");
}
/* FunctionDef.cpp */
#include <cstdio>void Function()
{printf("[Another Function]========= Get!!!\n");
}
int main() {Function();
}

 运行结果为

[Another Function]========= Get!!!

编译器优化开关打开时,运行结果正常不存在问题。

[Function]========= Get!!!

六.内联函数的类方法实现

大家都知道,C++相比于C来说,多了Class这一用法,增加了面向对象编程的思想,也有很多人称C++为C With Class,C++比C语言上多了类这一特性,从函数内联的角度上来讨论分析一下类实现内联函数和不使用类的C风格函数在效率上直观的区别。

1.C++类实现

C++中在类内定义的所有函数都自动称为内联函数(隐式内联)

内联函数本身也是一个真正的函数,具有普通函数的所有行为,唯一不同之处在于内联函数会在适当地方像预定义宏一样展开,在程序运行时不再进行函数调用和返回,从而消除函数调用和返回的系统开销,提高了程序执行效率。

#include <stdio.h>class Trace{
public:Trace(){noisy = 0;}void print(char *s){if (noisy){printf("%s", s);}}void on(){ noisy = 1; }void off(){ noisy = 0; }
private:int noisy;
};

对于Trace类来说,其中的成员函数定义在类内,C++会内联扩展他们

int main()
{Trace t;t.print("inline function");return 0;
}

2.C语言不用类实现

在C语言中,如果一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗。

#include <stdio.h>
static int noisy = 1;
void trace(char *s)
{if(noisy)printf("%s", s);
}
void trace_on(){ noisy = 1; }
void trace_off(){ noisy = 0; }int main()
{	trace("trace");return 0;
}

上述代码中还引入了更多的全局变量,C++只有一个,C有三个

五.内联与宏

第二节已经基本说明为什么使用内联函数代替宏,下面举一个例子说明。

在C程序中,可以用宏代码提高执行效率。宏代码本身不是函数,但使用起来象函数。预处理器用复制宏代码的方式代替函数调用,省去了参数压栈、生成汇编语言的CALL调用、 返回参数、执行return等过程,从而提高了速度。
使用宏代码最大的缺点是容易出错,预处理器在复制宏代码时常常产生意想不到的边际效应。宏看起来像函数调用,但没有参数类型及返回值,实际会有隐藏的难以发现的问题,例如执行ans = MyAdd(2, 3)*2时会返回2+3*2。

六.内联的局限性

内联能提高函数的执行效率,为什么不把所有的函数都定义成内联函数?
1.内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。(一般情况,在函数频繁调用且函数内部代码很少的情况下使用内联)
2.每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
3.类的构造函数和析构函数容易让人误解成使用内联更有效。要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。所以不要随便地将构造函数和析构函数的定义体放在类声明中。
4.一个好的编译器将会根据函数的定义体,自动地取消不值得的内联。对函数作inline声明只是程序员对编译器提出的一个建议,而不是强制性的,并非一经指定为inline编译器就必须这样做。编译器有自己的判断能力,它会根据具体情况决定是否这样做。一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline 不应该出现在函数的声明中)。具体是否会被编译器优化为内联也要看优化级别。有些函数即使声明为内联的也不一定会被编译器内联,这点很重要。比如虚函数和递归函数就不会被正常内联。通常,递归函数不应该声明成内联函数。(递归调用堆栈的展开并不像循环那么简单,比如递归层数在编译时可能是未知的,大多数编译器都不支持内联递归函数)。虚函数内联的主要原因则是想把它的函数体放在类定义内,为了图个方便,抑或是当作文档描述其行为, 比如精短的存取函数。将内联函数放在头文件里实现是合适的,省却你为每个文件实现一次的麻烦。而所以声明跟定义要一致,其实是指,如果在每个文件里都实现一次该内联函数的话,那么,最好保证每个定义都是一样的,否则,将会引起未定义的行为,即是说,如果不是每个文件里的定义都一样,那么,编译器展开的是哪一个,那要看具体的编译器而定。所以,最好将内联函数定义放在头文件中。

七.内联的使用建议

使用:

当对程序执行性能有要求时,那么就适当使用内联函数
当你想宏定义一个函数时,使用内联函数
写一些功能专一且性能关键的函数,这些函数的函数体不大,包含了很少的执行语句。通过inline声明,编译器不需要跳转到内存其他地址去执行函数调用,也不需要保留函数调用时的现场数据。
在类内部定义的函数会默认声明为inline函数,这有利于 类实现细节的隐藏。(但也需要斟酌如果不需要隐藏的时候,其实大部分是不推荐默认inline的)
不使用:

不使用:如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
不使用:如果函数体内出现循环或者开关语句;那么执行函数体内代码的时间要比函数调用的开销大。

八.内联和static

多数情况下,inline 前面会加static关键字。why?

分开理解:

static 意味着本地化,每个包含头文件的C文件均在本地产生一个独立的内联函数。当有多个C文件包含头文件时,不会因为函数名相同而报重定义错误。(代价就是 代码所占的空间会变大)

谨慎使用 static:如果只是想把函数定义写在头文件中,用 inline,不要用static。static 和 inline 不一样:

  • static 的函数是 internal linkage。不同编译单元可以有同名的static 函数,但该函数只对 对应的编译单元 可见。如果同一定义的 static 函数,被不同编译单元调用,每个编译单元有自己单独的一份拷贝,且此拷贝只对 对应的编译单元 可见。
  • inline 的函数是 external linkage,如果被不同编译单元调用,每个编译单元引用/链接的是同一函数,同一定义。
  • 上面的不同直接导致:如果函数内有 static 变量,对inline 函数,此变量对不同编译单元是共享的(Meyer's Singleton);对于static 函数,此变量不是共享的。看后面的代码就明白区别了。

static inline 函数,跟 static 函数单独没有差别,所以没有意义,只会混淆视听。


http://chatgpt.dhexx.cn/article/3CJEWCpo.shtml

相关文章

[C++] 内联函数inline 以及 auto关键字 -- C++入门(4)

本篇文章主要包括内联函数和auto关键字。其中&#xff0c;内敛函数包括概念&#xff0c;特性等&#xff1b;auto关键字的使用规则&#xff0c;使用场景等。 目录 1.内敛函数 1.1问题引入&#xff1a; 1.2内联函数的概念 1.3内敛函数的特性 2.auto关键字 2.1auto简介 2.2 auto的…

Windows误删注册表恢复方法

昨天不小心把注册表给删了,期间一直找解决方法,因为没有usb等重装工具... 我把注册表的HKEY_LOCAL_MACHINE\software这个重要的东西给误删了 ---结果就是软件打不开.就连删除东西都没用,自带的cmd什么的系统工具打不开... 关机重启问题更严重了,直接蓝屏,但是还好,开机的时候…

Linux 误删文件恢复命令及方法

你知道的越多&#xff0c;不知道的就越多&#xff0c;业余的像一棵小草&#xff01; 你来&#xff0c;我们一起精进&#xff01;你不来&#xff0c;我和你的竞争对手一起精进&#xff01; 编辑&#xff1a;业余草 http://r6d.cn/JzNf 推荐&#xff1a;https://www.xttblog.com/…

win10误删的注册表能还原吗_win10自带注册表恢复方法 win10注册表误删如何修复...

系统注册表&#xff0c;是计算机中一个很重要的部件&#xff0c;没有人可以保证自己能记住所有的注册表项&#xff0c;如果不小心让注册表被修改或者其他原因让注册表失效的时候&#xff0c;我们就可以通过cmd对注册表进行恢复&#xff0c;下面小编就为大家介绍win10自带注册表…

成功恢复 Linux 系统中已删除的文件

点击关注公众号&#xff0c;回复“1024”获取2TB学习资源&#xff01; 当用户意外地删除了一个仍然需要的文件时&#xff0c;大多数情况下&#xff0c;是没有简便的方法可以重新找回或重建这个文件。不过&#xff0c;幸运的是文件是可以通过一些方法恢复的。当用户删除了一个文…

windows注册表操作——备份,还原注册表,清除注册表卸载残留信息

注册表包含在操作过程中Windows不断引用的信息&#xff0c;例如每个用户的配置文件、计算机上安装的应用程序以及每个用户可以创建的文档类型、文件夹和应用程序图标的 属性表 设置、系统上存在的硬件以及所使用的端口。 序言 注册表是Microsoft Windows中的一个重要的数据库…

Linux下达梦误删除文件后恢复步骤

目录 数据文件恢复1.测试环境准备2.删除数据文件3.恢复步骤4.使用限制5.官方资料 redo日志恢复1. 删除redo日志2.启动数据库3.查看建库参数4.初始化新实例5.拷贝redo文件6.修改db_magic值7.启动数据库8.错误示范9.迁移数据 官方社区 数据文件恢复 1.测试环境准备 1.1准备好测…

windows注册表恢复方法

如果可以进入安全模式&#xff0c;您可以在安全模式内调用命令提示符输入命令修复一下系统组件。 在管理员命令提示符下键入以下命令&#xff1a; Dism /Online /Cleanup-Image /ScanHealth 这条命令将扫描全部系统文件并和官方系统文件对比&#xff0c;扫描计算机中的不一致…

linux环境下恢复rm误删的文件

文章目录 前言rm之后还有救吗使用foremost找回文件使用extundelete找回文件 预防误删引发的事故总结 前言 一提到在 linux 环境下删除文件&#xff0c;那绝对离不开 sudo rm -rf /* 这个梗&#xff0c;每次看到这个命令&#xff0c;我都想到一幅恶搞的图片&#xff1a; 这个『…

如何恢复 Linux 系统下被删除的文件 ?

丢失数据是任何用户都可能经历的最令人不安和痛苦的经历之一。一旦珍贵数据被删除或丢失&#xff0c;就再也找不不回来通常会引发焦虑&#xff0c;让用户感到无助。值得庆幸的是&#xff0c;有几个工具可以用来恢复 Linux 机器上被删除的文件。我们尝试了一些数据恢复工具&…

入门版Linux上恢复误删除的文件

一、被删除的文件正在被进程使用&#xff1a; 当某个文件正在被某个程序使用时&#xff0c;linux针对该文件有回两个计数器&#xff1a; i_count计数器&#xff1a;该文件可能被多个进程使用&#xff0c;每一个进程使用该文件&#xff0c;i_count数值都会加1。反之&#xff0…

不小心误删注册表exe,所有exe程序无法运行

首先我陈述下误删的原因吧&#xff0c;与其说是误删不如说就是自己有 意删除的&#xff0c;电脑Windows7&#xff0c;我在安装CASS10.1的时候&#xff0c;由于一个补丁程序始终不能运行&#xff0c;弹框显示说CASS10.1.6补丁.exe不是有效的Win32应用程序。然而我去寻找解决办法…

如何恢复Linux中的误删文件

写在前面的话 在开始教程之前我有必要提醒大家&#xff0c;使用窗口管理器&#xff08;GUI&#xff09;删除文件和使用命令行工具&#xff08;CLI&#xff09;删除文件这两种方法之间是有区别的。 当我们使用窗口管理器来删除文件时&#xff0c;我们仅仅只是将文件从某个目录移…

Win11注册表编辑器误删了如何恢复?

​ 注册表编辑器是一个用来更改系统注册表设置的高级工具&#xff0c;与资源管理器的界面很类似。近期有用户将注册表编辑器误删了&#xff0c;那么应该如何恢复呢&#xff0c;下面小编就给大家分享一下详细的恢复方法。遇到同样问题的用户注意了。 更多重装系统教程尽在小白…

注册表中exe被删除后恢复

恢复注册表 如果我们不小心将注册表中的exe删除后&#xff0c;这个时候不管打开什么软件都需我们自己进行指定才能打开使用&#xff0c;这样是及其麻烦的&#xff0c;而且在删除掉.exe之后&#xff0c;原来能在“运行”中搜索的执行文件也都无法执行了&#xff0c;比如果原来我…

LINUX使用rm误删文件后恢复

最近使用centos执行了rm -rf删除了一个文件&#xff0c;后面想恢复。 先关闭selinux vim /etc/selinux/config SELINUXdisabled reboot 重启生效1、使用debugfs命令恢复&#xff08;此命令为系统自带&#xff09; 1-1、查看文件系统类型&#xff0c;以下2个命令都可查看&…

电脑注册表误删恢复办法:系统文件和设置还原法

一.起因&#xff1a;为了修改电脑字体一不小心把Control Panel整个注册表给删除了&#xff0c;导致电脑界面变的锯齿&#xff0c;界面变形等各种问题&#xff0c;网上找了许多方法都没成功或者难度较大&#xff0c;最终使用系统恢复还原点将系统变成几个小时前的各种设置&#…

如何每天自动发送心灵鸡汤、正能量语录

为什么要发送心灵鸡汤、正能量语录 许多团队管理者或者行政为了需要鼓励团队&#xff0c;让员工有一个积极饱满的工作状态&#xff0c;需要每天在企业群内发送心灵鸡汤、正能量语录等信息&#xff0c;按照大多数人的办法&#xff0c;是在搜索引擎找到对应的心灵鸡汤或者正能量…

励志心灵鸡汤经典语录,满满都是道理!

1、【人生的幸运是靠努力而来的&#xff0c;世上没有不劳而获&#xff0c;天上掉馅饼的事&#xff0c;所谓种瓜得瓜&#xff0c;种豆得豆。别人可以替你开车&#xff0c;但不能替你走路&#xff1b;可以替你做事&#xff0c;但不能替你感受。人生的路要靠自己去走&#xff0c;成…

隐马尔可夫模型前向算法推导

已知条件有&#xff1a; 状态集合&#xff1a;&#xff0c;观测集合&#xff1a; 已观测到的观测序列 从t时刻的状态到t1时刻的状态的状态转移概率&#xff1a; 从t时刻的状态生成t时刻的观测的概率&#xff1a; 状态初始概率向量 给定隐马尔可夫模型 &#xff0c;定义到…