亚当2014年的演讲
鼓励人们,去尝试一下.干就完了
.
能用D
写一个内核吗?能.
接口中,不能有虚函数
,因为你不知道有多少槽
.
一旦重载
新(模板函数)运算符,如何放进类中?答案很简单:只需转发
它给虚函数
,你就有了这些命名函数.理念:实用为主.
在D中搞内核,处理器中跑的每行代码
都是你的,这很酷.
我们需要先编译个你好!
.从空object.d
开始,链接器中去掉运行时,看发生什么.大胆干,未定义引用
?没什么大不了的.
1
,缺少TypeInfo_Struct
,好,加1个.2
,大小不匹配,搜索该错误消息,发现在typinf.c
中说,需要52
字节.我用void*
静态数组,而别人用ubyte
数组.不断搜索,然后把要用的抓回来.如_d_arrayliteralSOMETHING
.然后搜索d运行时
,发现在lifetime.d
中.然后复制进你的object.d
中.你只需要大小名字
匹配,类型
不用匹配.
然后,你得到一个最小对象类
:Object,Throwable类,Exception,ModuleInfo构,及一些让编译器闭嘴的TypeInfos
.
简单用-nostdlib
选项,也不要C库
.最终得到非常小(3kb
)的可执行
文件.它能编译并运行
.
现在需要入口点
,这里有内联汇编
.也是我用dmc
原因,内联汇编
很好用.与独立汇编器
编写内容相同.它可能无法破坏
寄存器,或自动推导寄存器
上的C变量
类型,但他管用,且很易理解.
然后是,定义入口点
,编译器期望_d_run_main
函数.然后是在林操
上跑,定义了它后,调用exit(2)
系统函数.运行了,没有段错误
!
同样,如果写编译期
函数,如果不知道结果,你先按运行时
来写并调试,然后用mixin(插件)
,并用pragma(msg)
类似调试.编译时
运行很大的语言子集.所以运行时
管用的,基本只要稍微修改,编译时
也管用.
裸机
并不特殊,它只是运行代码而已.你可用GRUB
引导器,甚至不必改文件格式,只需要说,嘿GRUB
,加载这个ELF
文件,变得非常容易.我用dmd
开发.
运行起来后,可加越来越多函数.要区分object.d
及编译器
的功能.
首先是,alias string = immutable(char)[]
别名串
.d语言
中没有串
,但有字符数组
.串
只是object.d
中的别名
.
然后是typeinfo
,你得到要匹配大小
的类型信息,编译器输出它.对typeinfo_class
,你得到编译器
输出的初化数组
,及按静态数据
写的虚表
指针.
你必须要匹配它.我们是想让__traits
可用,
如,你想有classInstanceSize
特征,但你不能初化,如果你想创建类:1,分配内存.2,复制初始数据互它.3,调用构造函数
.这是new
的三步曲
.
如果不用typeid().init
,用trait
呢?如果typeinfo
是选入
的呢.你只要有你需要的初化器
.然后随意
布局,这样空的object.d
,就够了.
并非所有TypeInfo
都这样,用TypeInfo_Struct
时,然后加个整数组
,其有个叫next
的TypeInfo
成员.用typeid(数组)
,得到TypeInfo_Array
.而.next
为int[TypeInfo_i]
.也可用is()
来取.但我让编译器来搞.
整typeinfo
有趣的是,在rt/typeinfo
里面有大堆手写文件
.他说int
用4
大小覆盖.编译器现在都知道,有插件/循环
,现在可自动化
.
现在可以,foreach( TypeTuple!(所有内置类型))
,生成__traits
中拉出类
来并插件
起来造你的TypeInfos
.
仔细看看TypeInfo_i
名字等,这是混淆
名.i
为整
的混淆
.Aya
是不变符数组
的混淆
名.编译器知道它,当你插件type.mangleof
时,会形成它.相当于说编译时
构造类型
,你不必手动
写运行时
类型.
扩展
它很有意思,如果以typeinfo
为关联数组
的键
,你不必修改运行时
,就可扩展.如,你想按串
打印任意类型
数据.用模板,从std.conv调用to!string
即可.
但,就像d运行时
,你只一块
数据,多数函数为两个参数
:空*数据+类型的TypeInfo(类型信息)
,用类型信息
来取数据大小
及如何操作
.
不用模板,造你自己的toString
函数.提供空*
为参.你只有普通接口
及可任意扩展
的类
.即如何在编译时特征和运行时反射
间架桥.
类似的是可为每个单独类型创建静态构造器
.在object.d
中,为RTInfo
.只是现在enum RTInfo = null
.每个用户定义类型都实例化
它.在此,你可静态检查
类型,可运行时反射
,而不必用lint
工具.
如在object.d
中挂钩该rtinfo
,则直接应用于整个项目
,而不是单个模块
.库可定义自己的检查插件
,单独放进去,虽然麻烦,但管用.
只要安装了反射
,就可运行时
一直用(查询).可对模块
定义静态构造列表
,编译器
自动为你合并
,可建立
全局数据,供以后使用.
静态构造函数
实际上是D
运行时功能,编译器输出模块列表
,有个_Dmodule_ref
符号,链接器把它们放一堆,循环遍历时,编译器提供单元测试/静态构造/静态析构
指针,如果去掉运行时
,得你自己干这些.
回到程序入口点
,你要先找到数据
,循环并找到并跑它,然后来到实际主
函数,只需要20KB
,不大.
类
需要大量运行时
.类似的,如_d_dynamic_cast
调用动态转换
.
其,查找typeinfo[虚表0项]
,问是否是其他类的基
,是,则继续并返回偏移对象
.用_d_newclass
分配新类.
D运行时
,管理内存.垃集
为不懂析构器
的人准备的,在C++
论坛,我会说你只要定义个带有析构函数的结构
,垃集
也是如此,你不再关心所有权
.
有了垃集
,只需要完成它
,下一件事.相反.我写了个压指针
内存分配器.分配
好写,释放
难.一旦写好,你可在裸机
上用类
了.很多东西都有效.
虚函数/类成员
都行.线程本地存储
就不行了.操作系统不能
为你定义线程缓冲区
.
幸运,D
还支持shared
和__gshared
,一旦你这样做,现在可访问你放静态数据/全局变量
下面堆栈段
,并完成它.即使设法实现其余运行时
功能,他们也会假设你有TLS
.
d运行时
立即分配线程本地
数据,然后垃集
为你分配线程上下文
,正常时没问题,但你没有线程/垃集
时,很烦人.
还要了解
从哪释放
内存.可用scope(exit/failure/success)
,在使用时就保证
分配.
要准备好按C
方式写.Phobos
用一个函数,引入大量模块.
写simpledisplay.d
时,避免了标准库
.6000
行都很快.而标准库
太慢.D1
还没意识到,就编译完了.
运行时,禁用不变量和检查边界
,_d_boundscheck
和_d_assertm
,然后30kb
的ModuleInfo
.裸机
时用-release和-noboundscheck
编译.否则要定义invariant.d
等等函数,很麻烦.
裸机最有趣是中断处理
,循环(失败->重启)
.因而用内联汇编
,无栈/无返回
,所有都得自己写.
即使写微优化代码
,安装栈帧
,多次调用仍成本高,你要内联它.
进入中断器
后,你要有函数指针+中断程序
.你要保留使用的寄存器,处理中断,用(简单调用out的)中断控制器确认它
,然后用iret
返回,跑程序.
三个错误
,你要反汇编,iretd
.有时dmd
在32
位上输出
16位中断码.对双精
,你要显式写iretd
.
你忘记确认中断
,又要重来.
下一步
是告诉处理器中断在哪里
.x86架构
内存布局很怪
,与8080
兼容,然后不断加新东西.所以不是简单函数指针,分为三部分低16位,标志
,然后有32位
,低8位/高8位
等.
三个错误
,是结构对齐方式
问题,D
的align/align(1)
.现在align(1) struct InterruptLocation
对齐.一个是字段对齐
,一个是结构对齐
.
加载了中断,就该使用硬件了.它是映射内存
的,只是个神奇的数组
.
如在0xb8000
指定位置写串
,就看见字母
了.重载运算符
不需要运行时
.编译器只是重写
为常规函数.
你可用内联汇编
,你可写小插件
函数来生成汇编
,然后在高级
层次上写
他们.你仍然是在写汇编
.用D元编程
来写汇编
.
内联汇编
只是让你无限灵活
之一,irc
上有人说想要标签地址
作为变量.但这很丑,你可在运行时
用$
取它.你可这样取裸函数
地址.
你传递
闭包给分配
内存函数时,没有垃集
,如何取地址
.你不要管,没有_d_allocmemory
时,让它成为链接器错误
.
用闭包
最佳方法是写自己的构或类
,复制你特别需要数据,然后发送那个小对象
.这样,很清楚发生什么,想要什么,需要什么
及能够控制所有权,只是没内置函数
好看.
继续写_d_allocmemory
函数并返回块.如何释放
?则分闭包
为两个部分函数+数据
指针.函数指针
只是pod
,数据指针可能为类/构/(指向复制到堆上的)随机栈帧
指针,从数据
中检查_d_allocmemory(a)
.我知道a
来自的堆.如果在内存节
内,则释放,否则,交给别人处理.
_d_string_switch
函数,串上切换,它给你串针
及编译时选项
,编译器排序了数组,你可二分
搜索它.
自定义
运行时,可了解系统
是如何工作的.看到错误时,就知道去哪里找,如AA
,完全是可怕
的.一半像普通数组有typeinfo+指针
,另一半在object.d
中写啥也没干的关联数组(构)
.但如果没有后者,又链接
不过,就会变成未定义Aya等等
标识符.
我的cgi.d
,最下面有void workAroundLinkerErrors
,然后变成writeln(typeid())
,来隐藏AA
复杂性.太难了,太丑了.
知道函数名
,可迅速定位,如gc_malloc
.置断点,运行程序
一气呵成.然后看见堆栈上有_d_arrayliteralX
函数,数组字面中静态数据
导致动态分配?是的.所以,就要小心,要在定义数组时字面静态
调用它,来摆脱分配.
对构的填充字节
,你可用.offsetof和.sizeof
.你放他们进串
中然后用pragma(msg)
,编译器输出小图
.你可用pragma(msg)
输出图表,可用CTFE
串读回它们.
我告诉D作者
,串插件就像汇编
,很易学习.看见mov EAX, 0
,就是移动至寄存器
.但如何构建程序
(栈帧/算法
)就难了.
为利用串插件
,我喜欢编写个普通旧解析器
,去掉串
,构建抽象语法树
,并在其上操作,然后转换回串
并打印.分解为令牌,解析,操作,转串
更容易,而不是直接操作一大段代码串
.虽然代码更多,但维护性
更好.而不是到处都是连接(~)运算符
.~
是邪恶的.生成大量临时对象.不要用它,而是重载~=
.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现