C语言的“编译时多态”

typeof 在 kernel 中的使用 —— C 语言的“编译时多态”

C 语言本身没有多态的概念,函数没有重载的概念。然而随着 C 语言编写的软件逐渐庞大,越来越多地需要引入一些其他语言中的特性,来帮助更高效地进行开发,Linux kernel 是一个典型例子。

在动态类型的语言里面,往往有 typeof 这种语法,来获取变量的数据类型,比如 JavaScript 当中,typeof 以字符串型式返回了这个变量的数据类型,借由这种特性,往往可以根据传入参数的类型不同,产生不同的行为。

GCC 提供的 typeof,实际上是在预编译时处理的,最后实际转化为数据类型被编译器处理。用法上也和上述语言不太一样。

基本用法是这样的:

int a;
typeof(a) b; //这等同于int b;
typeof(&a) c; //这等同于int* c;

那么在内核中这种特性是怎样使用的呢?

/** Check at compile time that something is of a particular type.* Always evaluates to 1 so you may use it easily in comparisons.*/
#define typecheck(type,x) \
({  type __dummy; \typeof(x) __dummy2; \(void)(&__dummy == &__dummy2); \1; \
})/** Check at compile time that 'function' is a certain type, or is a pointer* to that type (needs to use typedef for the function type.)*/
#define typecheck_fn(type,function) \
({  typeof(type) __tmp = function; \(void)__tmp; \
})

这两段代码来自于 include/linux/typecheck.h,用于数据类型检查。

宏 typecheck 用于检查 x 是否是 type 类型,如果不是,那么编译器会抛出一个 warning(warning: comparison of distinct pointer types lacks a cast); 而 typecheck_fn 则用于检查函数 function 是否是 type 类型,不一致则抛出 warning(warning: initialization from incompatible pointer type)

原理很简单,对于 typecheck ,只有当 x 的类型与 value 一致,&__dummy == &__dummy2 的比较才不会因为类型不匹配而抛出 warning ,详情可以参考 C 语言对于指针操作的标准规定。对于 typecheck_fn ,当然也只有 function 的返回值和参数表与 type 描述一致,才不会因为类型不匹配而抛出 warning 。

到这里有人可能会有一个疑问,内核代码里执行类型检查会不会降低效率?答案是不会的,因为实际上,这些为类型检查而声明的临时变量,实际上在上下文中都没有使用,并且还特别地强制类型转换为 void 防止任何由这些临时变量产生的结果被使用的情况,因此在编译器优化时,就将这些无用的代码删除了。

然后 kernel 中还定义了使用另一种类型检查策略的获取最大最小值的宏。

/** ..and if you can't take the strict* types, you can specify one yourself.** Or not use min/max/clamp at all, of course.*/
#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; })

这个例子里面不要求 x 和 y 是严格等于 type 类型,只要 x 和 y 能够安全地完成隐式类型转换为 type 就可以安全通过编译,否则会抛出 warning。

另外一个非常经典的例子就是交换变量。

/** swap - swap value of @a and @b*/
#define swap(a, b) \do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)

试想如果没有 typeof,要怎么在 C 语言中实现这种类似 C++ 模板的特性呢?

这里还有一个功能和 typeof 类似的运算符: typeid

typeid 是为 RTTI(运行时类型检查) 提供的第二个运算符。

  • typeid 运算符允许在运行时确定对象的类型

  • 返回的结果是 const type_info&

  • typeid 运算符在应用于多态类类型的左值时执行运行时检查,其中对象的实际类型不能由提供的静态信息确定;

  • typeid 也可以在模板中使用以确定模板参数的类型

  • typeid 是操作符,不是函数,运行时获知变量类型名称;

和 typeof 的主要区别有二:

  • typeof(编译器提供) 是一个编译时结构,并返回编译时定义的类型,和 C++11 提供的关键字 decltype 类似
  • typeid( C++ 提供) 是一个运行时结构,因此提供了有关该值的运行时类型的信息

typeid 表达式的形式是 typeid(e), 其中 e 可以是任意表达式或者类型的名字。 typeid 操作的结果是一个常量对象的引用,该对象的类型是标准库类型 type_info 或者 type_info 的公有派生类型。如果表达式是一个引用,则 typeid 返回该引用所引对象的类型。不过当 typeid 作用于数组或函数时,并不会执行向指针的标准类型转换。也就是说,如果我们对数组 a 执行 typeid(a) ,则所得的结果是数组类型而非指针类型。

当运算对象不属于类类型或者是一个不包含任何虚函数的类时, typeid 运算符指示的运算对象的静态类型。而当运算对象是定义了至少一个虚函数的类的左值时, typeid 的结果直到运行时才会求得。

当 typeid 作用于指针时(而非指针所指的对象),返回的结果是该指针的静态编译时类型

typeid 是否需要运行时检查决定了表达式是否会被求值。只有当类型含有虚函数时,编译器才会对表达式求值。反之,如果类型不含有虚函数,则 typeid 返回表达式的静态类型;编译器无须对表达式求值也能知道表达式的静态类型。

RTTI 栗子:

class Base {friend bool operator==(const Base&, const Base&);
public:// Base 的接口成员
protected:virtual bool equal(const Base&) const;// Base 的数据成员和其他用于实现的成员
};class Derived : public Base {
public:// Derived 的其他接口成员
protected:bool equal(const Base&) const;// Derived 的数据成员和其他用于实现的成员
};// 类型敏感的相等运算符
// 接下来介绍我们是如何定义整体的相等运算符的:
bool operator==(const Base &lhs, const Base &rhs) {// 如果 typeid 不相同,返回 false;否则虚调用 equalreturn typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
}

在这个运算符中,如果运算对象的类型不同则返回 false。否则,如果运算对象的类型相同,则运算符将其工作委托给虚函数 equal 。当运算对象是 Base 的对象时,调用 Base::equal ;当运算对象是 Derived 的对象时,调用 Derived::equal 。

虚 equal 函数

继承体系中的每个类必须定义自己的 equal 函数。派生类的所有函数要做的第一件事都是相同的,那就是将实参的类型转换为派生类类型:

bool Derived::equal(const Base &rhs) const {// 我们清楚这两个类型是相等的,所以转换过程不会抛出异常auto r = dynamic_cast<const Derived&>(rhs);// 执行比较两个 Derived 对象的操作并返回结果
}

上面的类型转换永远不会失败,因为毕竟我们只有在验证了运算对象的类型相同之后才会调用该函数。然而这样的类型转换必不可少,执行了类型转换后,当前的函数才能访问右侧运算对象的派生类成员

type_info 类

type_info 的操作

  • t1 == t2 如果 type_info 对象 t1 和 t2 表示同一种类型,返回 true, 否则返回 false

  • t1 != t2 与上一条相反

  • t.name() 返回一个 C 风格的字符串,表示类型名字的可打印形式。类型的名字生成方式因系统而异

  • t1.before(t2) 返回一个 boo 值,表示 t1 是否位于 t2 之前。 before 所采用的顺序关系是依赖于编译器的。

一般 type_info 是作为一个基类出现,所以应该提供一个公有的虚析构函数。当编译器希望提供额外的类型信息时,通常在 type_info 的派生类中完成。

type_info 类没有默认构造函数,而且他的拷贝和移动构造函数以及赋值运算符都被定义成删除的。因此我们无法定义或拷贝 type_info 类型的对象,也不能为 type_info 类型的对象赋值。创建 type_info 对象的唯一途径是使用 typeid 运算符

栗子:

int arr[10];
Derived d;
Base *p = &d;std::cout << typeid(42).id() << ", "<< typeid(arr).name() << ", "<< typeid(Sales_data).name() << ", "<< typeid(std::string).name() << ", "<< typeid(p).name() << ", "<< typeid(* p).name() << std::endl;

在作者的计算机上运行该程序,结果如下

i, A10_i, 10Sales_data, Ss, P4Base, 7Derived

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/448059.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

看脸色知体内各积毒 有效清洁内脏妙方

观察下五脏六腑是否中毒。 淤血、痰湿、寒气这些不能及时排出体外&#xff0c;危害健康和精气神的物质&#xff0c;中医称之为毒素&#xff0c;在镜子里你也可以看出它们。识别之后&#xff0c;你更需要有效的内脏清洁妙方! 症状一&#xff1a;面色青两侧长痘黄褐斑愁云满面…

UTC Time

整个地球分为二十四时区&#xff0c;每个时区都有自己的本地时间。在国际无线电通信场合&#xff0c;为了统一起见&#xff0c;使用一个统一的时间&#xff0c;称为通用协调时(UTC, Universal Time Coordinated)。UTC与格林尼治平均时(GMT, Greenwich Mean Time)一样&#xff0…

解决:Unknown custom element: <myData> - did you register the component correctly? For recursive compon

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 引用一个组件报错&#xff1a; Unknown custom element: <myData> - did you register the component correctly?For recursi…

无处不在的container_of

无处不在的container_of linux 内核中定义了一个非常精炼的双向循环链表及它的相关操作。如下所示&#xff1a; struct list_head {struct list_head* next, * prev; };ubuntu 12.04 中这个结构定义在 /usr/src/linux-headers-3.2.0-24-generic/include/linux/types.h 中&…

程序员学习能力提升三要素

摘要&#xff1a;IT技术的发展日新月异&#xff0c;新技术层出不穷&#xff0c;具有良好的学习能力&#xff0c;能及时获取新知识、随时补充和丰富自己&#xff0c;已成为程序员职业发展的核心竞争力。本文中&#xff0c;作者结合多年的学习经验总结出了提高程序员学习能力的三…

时间,数字 ,字符串之间的转换

package com.JUtils.base;import java.sql.Timestamp; import java.text.SimpleDateFormat;/*** 转换工具类<br>* 若待转换值为null或者出现异常&#xff0c;则使用默认值**/ public class ConvertUtils {/*** 字符串转换为int*** param str * 待转换的字符串* param …

宏定义及相关用法

宏定义及相关用法 欢迎各位补充 目录 一些成熟软件中常用的宏定义&#xff1a;使用一些内置宏跟踪调试&#xff1a;宏定义防止使用时错误&#xff1a;宏与函数 带副作用的宏参数 特殊符号&#xff1a;’#’、’##’ 1、一般用法2、当宏参数是另一个宏的时候 __VA_ARGS__与##…

解决:Cannot read property ‘component‘ of undefined ( 即 vue-router 0.x 转化为 2.x)

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 vue项目原本是用0.x版本的vue-router&#xff0c;但是去报出&#xff1a;Cannot read property component of undefined 这是因为版本问…

AMD Mantle再添新作,引发下代GPU架构猜想

摘要&#xff1a;今年秋天即将发布的《希德梅尔文明&#xff1a;太空》将全面支持AMD Mantle API&#xff0c;如此强大的功能背后离不开强大的CPU、GPU支持。上周AMD爆出了下一代海盗岛R9 300系列&#xff0c;据网友猜测海盗岛家族可能用上速度更快的HBM堆栈式内存。 小伙伴们…

不作35岁的程序员?

程序员三部曲--不作35岁的程序员?摩西2000 在中国&#xff0c;程序员不能超过35岁&#xff0c;似乎已经是不争的事实&#xff0c;软件开发工作就是青春饭&#xff0c;顶多靠毕业这十年的时间&#xff0c;超过这个年龄&#xff0c;要不成功跃身成为管理者&#xff0c;要不转…

linux下使用TC模拟弱网络环境

linux下使用TC模拟弱网络环境 模拟延迟传输简介 netem 与 tc: netem 是 Linux 2.6 及以上内核版本提供的一个网络模拟功能模块。该功能模块可以用来在性能良好的局域网中,模拟出复杂的互联网传输性能,诸如低带宽、传输延迟、丢包等等情 况。使用 Linux 2.6 (或以上) 版本内核…

CDN 是什么 、CDN 引入

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 CDN 的全称是 Content Delivery Network&#xff0c;即内容分发网络。 CDN的基本原理是广泛采用各种缓存服务器&#xff0c;将这些缓存…

长寿的人会有的8个健康理念

长寿的人会有的8个健康理念。年轻的时候总是在挥霍身体健康&#xff0c;吸烟、喝酒没有节制&#xff0c;到老了之后身体会出现各种问题。老年人如果想要身体健康、长寿的话&#xff0c;就要从日常生活习惯做起。下面小编就来介绍长寿的人会有的8个健康理念&#xff1a; 1、少…

Ubuntu下selenium+Chrome的安装使用

Ubuntu下seleniumChrome的安装使用 安装 chrome 官网下载安装包 sudo dpkg -i google-chrome-stable_current_amd64.deb whereis google-chrome 安装selenium pip3 install selenium 下载chromedriver(火狐使用geckodriver)驱动 http://npm.taobao.org/mirrors/chromed…

shoot for用法

Look, there are people like Ross who need to shoot for the stars, with his museum, and his papers getting published.---《老友记》 而像罗斯这种人则追求卓越&#xff0c;博物馆&#xff0c;发表论文。 争取;为...而努力Were shooting this year for a 50% increase in…

VUE : 双重 for 循环写法、table 解析任意 list 、万能表格组件、解析一维数组、动态生成 table 所有数据

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1.需求&#xff1a; 我想要一个 table 组件能在实际调用时动态生成所有的 tr 、td 。 后端返回的只是一个 list &#xff0c; 前端页…

安全离职妙招

高招的离职&#xff0c;不但有可能让前老板帮你说好话&#xff0c;让前同事成为你的啦啦队&#xff0c;未来若有好机会&#xff0c;还会想到你&#xff0c;只要你学会克服离职流程中的五个尴尬情境。 情境一、离职怎么提&#xff1f; 口头请辞&#xff0c;最先告知上司。 有…

字节内推~

大佬们有兴趣来字节约饭么&#xff0c;下面是内推链接~ 社招内推链接&#xff1a;https://job.toutiao.com/s/LwpKWU8 校招内推链接&#xff1a;https://job.toutiao.com/s/LwsFw6g

使用编辑工具快速创建实体对象的方法

快速创建java类 (\w)\s(.) /** $2 */\nprivate String $1; search Mode 为 Reqular expression 转载于:https://www.cnblogs.com/otways/p/11283303.html

超详细 图解 : IntelliJ IDEA 逆向生成 JAVA 实体类

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1.配置数据库,&#xff0c;这里连接的是mysql。 2.填写 连接数据库的信息&#xff0c;填写完成后可以点击Test Connection,测试一下是否…