Browsed by
月份:2016年7月

提供给计算机,软件专业的资料

提供给计算机,软件专业的资料

入门篇

提问篇

提问前请看提问的艺术 这里是简体中文版, 下面有英文版

学习计算机专业, 自主能力非常重要, 学会多思考, 没有经过自己深思熟虑, 并且亲自尝试试验的问题不要提出来, 这样就算有人回答了你, 你也没有学到任何东西, 拒绝伸手党, 强烈建议大家阅读 提问的艺术 一文, 本博客内有该文章的转载, 链接在这里

如果你看不进去那么长的文章, 那么你基本不适合学习计算机, 如果你能按照提问的艺术中讲到的去做, 那么你的自学能力会有很大的提高

语言篇

入门必看: 强烈推荐所有的, 无论对计算机有了解还是没有了解的新生, 去看 哈佛大学的公开课: 哈佛大学CS50, 在网易公开课就有, 链接自己搜索一下

入门语言强烈建议为C语言, C语言是面向过程的语言, 没有类的概念, 入门C语言学会控制流(顺序, 分支, 循环 三大结构) 以及递归的思想, 还有初步了解指针的含义, 就算做C语言入门了 推荐大家去Online Judge 做做简单的题目, 锻炼自己的编程能力, 下面推荐一些书籍

首先要说明的是, 一定不要看谭浩强的C语言入门教材, 本教材的代码严重不符合规范, 且老套

推荐书籍:

C语言入门经典 http://item.jd.com/11362614.html (大家以后购买计算机书籍, 认准黑色书皮的,  以及国外的经典作品)

(华章教育的图书, 图灵丛书, 都是非常好的计算机资料的出版社)

C Primer Plus http://item.jd.com/10062260.html  适合对编程有一点感觉的新生来看, 不然的话可能看不进去

C 和指针 http://item.jd.com/10062667.html 适合对C语言已经有一定了解, 并且能够熟练编写100行左右的C语言程序的同学看, 深入对C语言的指针进行了解

C 陷阱和缺陷 http://item.jd.com/10062654.html 适合已经写过5000行+的C语言代码的同学看, 本书介绍了很多C语言的特性和问题, 给大家指出了很多在写C语言的时候可能遇到的坑

看完这些书, 你对C语言基础(强调!!!! 是C语言基础, 不是C语言, C语言远比大家想象的复杂) 已经非常熟练了, 不过这远远不够, 只会语言不会算法的话, 是写不出来什么有用的程序的

 

算法&数学篇

对于编程语言有一定了解之后, 就应该去学习算法, 算法是程序设计的思想, 像你们之前学到的冒泡排序, 还有二分搜索都是简单的算法, 除了这些之外, 想要写出高效, 可靠的程序,需要对算法有系统的学习, 下面推荐一些算法方面的书籍

啊哈,算法 非常非常基础,非常非常科普向的一本面向小白的算法书

算法导论 算法界的经典著作, 适合入门看, 不过需要付出很大的努力才能吃透

大话数据结构 生动形象地讲解了常用的数据结构, 里面的代码均可正常运行

具体数学 计算机专业的数学经典, 适合锻炼思维, 学习思想

算法竞赛入门经典 & 配套的训练指南 这个是面向想要参加算法竞赛的同学,  本书适合作为算法竞赛的入门书籍, 配套的训练指南中的题目难度有相当的难度, 适合提高使用

[因为我懒我就没提供商品链接]

原理篇

在你将算法 数据结构 & 编程语言都掌握的比较熟练之后, 对于计算机的原理有一个很好的理解就是提高的关键, 这里不列举书籍, 因为对于大家这个目标还过于久远,而且, 能完成我之前说的那些,达到那个水平的同学, 都应该具备了自己获取优质资料的能力, 在此给几个原理的方向

编译原理 了解将代码转变为机器语言的整个过程, 如何根据特定的平台进行优化 难度: 很高

操作系统原理: 了解整个操作系统的原理, 推荐网站 wiki.osdev.org 这个网站有教程引领大家写一个操作系统出来 难度:非常非常高(不要笑, 涉及原理的东西没有简单的)

数据库原理: 介绍略

.掌握一个领域的原理, 你就可以说你已经在那个领域达到一个相当的高度了~

 

资料篇

下面提供一些非常优质的计算机&软件专业的(或者说程序员们)不应该错过的网站

github.com 具体是什么自己去了解

coursera.org edx.org 国外两大MOOC网站

codecademy.com 在线学习不同编程语言的网站 需要科学上网

codecombat.com 一个有趣的通过编程玩游戏的网站(需要科学上网)

https://www.jisuanke.com/ 国内比较好的在线编程学习平台, 建议从入门学起, 根据自己接受能力逐渐学习

https://www.shiyanlou.com/ 国内比较好的在线编程学习平台, 如果大家对Linux感兴趣, 这里有很不错的课程, 可以不用自己安装双系统, 就进行Linux的学习, 在线操作Linux操作系统, 另外还有很多其他的课程, 都很不错

google.com bing.com 好用的搜索引擎

因篇幅有限,在此仅列举这些网站, 还有非常多的好的网站大家自行寻找,欢迎分享给大家, 技术是需要分享才能进步的

安利篇

最后, 如果你是一个对技术非常感兴趣的同学, 热爱计算机技术, 自学能力强, 欢迎加入我们东北大学先锋网络中心网络部 下面的链接是对我们部门的一个介绍

https://neup-net-depart.github.io/Welcome-to-NEUP-Net-Depart/

另外如果你自己认为自己是一个geek, 欢迎加入NEUP Geek群, 我们提供一个优质的技术交流环境~ 欢迎一起交流, 群号

就在上面链接的最下方

 

资源篇

这里整合所有窝认为很好的资源,供自己查看&分享他人

C Puzzles

数百个具有挑战的逻辑谜题

千里码,一个在线编程问题解决平台

程序员游戏

程序员游戏-再

awesome-awesomeness

[不是科普向?] RE: 从零开始的操作系统开发 第二集

[不是科普向?] RE: 从零开始的操作系统开发 第二集

[本文概念性内容较多, 看图党慎入]

概要

Hmm, 第一集中我们已经学会了如何让计算机在启动的时候加载并运行我们写好的bootsector, 并且通过使用BIOS提供的中断控制显卡在屏幕上输出特定的字符串, 慢慢的我们已经熟悉了16bit下的底层程序的开发, 不过就如我们综述中所说, 16bit下我们能够访问的内存十分有限, 因此我们不得不离开这个我们熟悉但是却不好用(qwq)的16bit模式,切换到32bit下, 切换之后我们就可以在C语言内继续我们的操作系统的开发了, 我们的引导程序实际上做的事情就是, 将模式切换到32位,并且将操作系统所在的磁盘扇区装载到内存里, 然后call我们的操作系统的main函数所在的地址, 这样, 整个引导程序的使命就完成了, 下面的工作就是操作系统的事情了, 本文我们主要介绍如何切换到32bit下的具体操作, 以及在32bit下控制显卡输出特定的字符

本文最后实现的效果的具体代码在github上均可获得 代码仓库地址见第一集

全局描述符表(Global Descriptor Table GDT)

我们已经熟悉了16bit下的内存的分段结构以及逻辑地址转换为内存的物理地址的方式, 现在再来回顾一下, 16位下我们想访问内存需要提供两个信息, 段地址+偏移量,  这两个信息决定了物理内存的地址, 决定的方法是 Segment Addr * 16 + Segment Offset  这个可以在16进制上形象的表示为 Segaddr << 4 + Offset, 那么现在我们要切换到 32bit保护模式下, 之前的这个内存的段-偏移量模型已经不适用了, 取而代之使用的是个功能更加强大的模型, 支持对内存的某个部分进行保护, 权限控制, 以及提供虚拟地址访问的内存模型. 另外,在CPU由实模式(16bit, 下略)切换到保护模式(32bit, 下略)下后, 将逻辑地址映射到物地址的方式也发生了改变, 这个改变, 和我们这个小标题提到的GDT密切相关, 下面我们就来具体的介绍一下 GDT

 

什么是GDT

GDT是由多个SD组成的一个数据结构, 每一个SD, 称作Segment Descriptor, 包含了一个段的各种元信息如下:

  1.  基地址(BaseAddress) 定义了这个段在内存中的物理的起始地址
  2.  段上限(Segment Limit)定义了段的大小
  3. 标志位数组, 这些标志位向CPU说明CPU应该如何处理这个段中的内容, 如本段只读, 或者本段可执行等, 相当于这个Segment的属性

每一个SD是一个8Byte长的数据结构, 通过64个bit存储了所有上述信息, SD的结构如下:

GDT_Entry

我们可以看出来比较反人类的地方是这个数据结构存储的不同类型的数据信息并不是连续的= = 推测是因为为了向下兼容才这么做的, 具体的原因为何还没有调查过, 从上述结构中, 如果我们想查询出这个SD对应的段的基地址, 我们需要将SD的16 — 31 ,32 — 39 56 — 63位拼接到一起, 得到基地址的值. 我们再来看一下这个结构, 这个结构中描述了 BaseAddr, Limit 还有Flags& AccessByte, 其中的Flags & AccessByte如下图所示

SD(本图出自 该链接)

这里的Flag的bit位比较多, 我们挑几个重要的介绍一下

L位是表示我们的内存使用64bit的代码段模式还是32bit, 这里我们使用32位, 因而此为置零,

DPL 指的是该段的代码拥有的CPU的权限级别,因为我们现在要使用程序直接操作CPU, 编写的操作系统也要操作CPU的很多特权指令, 因而该段的权限设置为 最高权限(ring 0, highest privilidge)

P 指的是这个段在内存中是否存在(?待确定)

具体每一个位表示什么意思的细节我们先不深究,想要了解的孩子可以看 “Intel® 64 and IA-32 Architectures Developer’s Manual: Vol. 3A”, 下面我们要使用汇编语言定义整个由DATA_SEG和CODE_SEG组成的GDT, 代码如下

我们定义了两个段, 而且这两个段是重叠在一起的, 基地址相同, 大小也相同, 只不过一个是代码段一个数据段, 为了简便起见, 我们目前只需要这两个段即可, 通过代码我们可以看出来, 在两个段的上面还有一个全为0的段, 是用来做保护的,防止用户使用未初始化过的段寄存器访问内存获得随机脏数据. 具体为何说这个是用来保护的, 我们下面马上就要介绍

 

保护模式下的逻辑地址和物理地址的对应关系

在继续之前, 我们先补充一个刚刚没有说到的一个问题, 即目前的逻辑和物理地址是如何对应的.

在切换到保护模式下之后, 我们要指定访问的内存需要给出的是Segment Index + 偏移量, 之前在段寄存器中存储的是段地址, 目前这个地址变为了Segement Index,所谓的Segment Index的意思其实就是, 我们要访问第几个段, 然后, CPU会将GDT中对应段的描述符取出来, 找到这个段的基地址(Base Address),以及获得它的各种状态位, 然后加上提供的偏移量,最后对应到物理地址.  下面我们来解释一下为何要设置64bit的0在GDT的开头, 因为, 如果错误的使用了某个未初始化的段寄存器, 这个段寄存器的内容为 0 的话, 如果不设置64bit的0的话, 就会默认去找GDT里的第一个Entry 也就是我们定义的CODE_SEG段,这样就会读到开发者不期待得到的数据, 而且因为这个行为是合法的, 允许访问第零段,不会报错, 但是, 这个访问对于用户来说是没有意义的,因而这个地方会对之后的程序造成不可估量的影响, 那么合理的解决办法是, 我们让第零段变为无意义的段, 这样只要访问第零段就会直接报错, 因而我们将第零段设为了64bit全为零, CPU在访问到这里的时候就会panic而终止掉下面的执行

 

切换到保护模式需要做的另一项工作

设置好了GDT之后, 我们要将这个表使用lgdt装载, 为了装载GDT,我们需要一个用来描述我们GDT的结构, 叫做GDT-descriptor, 这个描述符只存储两个信息, 即GDT的地址, 以及GDT的size – 1, 为什么是size – 1 而不是size大家可以思考一下. [实际上, 我们不会将GDT descriptor的size属性设置为0,那样表示指向一个空的GDT, 没有意义, 而且, GDT Descriptor 能够表示的GDT的大小在 0 — 65535之间, 如果我们的GDT大小是65536的话, 就无法表示, 这样我们就可以做个平移, 让 GDT存储大小的时候 0 表示 1 , 65535表示 65536.]

设置好这个GDT Descriptor之后, 我们将这个地址作为操作数, 传递给 lgdt指令, 就可以将GDT装载好了

 

以上是我们切换到保护模式下要做的最重要的一步,  切换到保护模式下还要做一些其他的操作, 首先就是 “关中断” , 保护模式下中断的实现方式和实模式下的完全不同, 如果我们还使用由BIOS提供的这套简单的中断(实际上是提供的中断向量表), 就会出现问题, 因而在我们自己设置好中断向量表之前, 不应该允许中断的发生

关闭中断之后, 我们就可以装载我们上边准备了好久的GDT了, 装载了GDT之后, CPU才知道保护模式下该如何寻址

然后, 打开保护模式切换的开关, 我们要将控制寄存器 CR0的最低位置为 1 , 开启保护模式.

开启保护模式之后, CPU流水线中的所有在执行, 等待执行的指令, 或者缓存的微指令,这些信息都是实模式下的逻辑处理的, 而我们现在切换为了保护模式, 为了保证不让这些缓存影响CPU的正常运行, 我们需要flush缓存 ,  为了flush缓存,我们需要执行一个long jump,注意,这里跳多远不重要, 重要的是”如何跳”, 我们使用long jump跳到当前指令的下一条也可以, 跳到比较远的地方也可以, 但是 使用short jump跳到比较远的地方, 也不会flush缓存, 这里主要是为了让long jump对缓存的flush起作用 , 同时为了方便起见,这个long jump我们一般就直接跳到32bit模式下的入口代码的地址了~

为了完成以上操作, 实现的代码如下

 

在在保护模式下对显卡进行操作

现在我们已经切换到保护模式下了, 很明显我们不能使用中断来操作显卡了, 不过幸运的是, 我们可以直接操作显卡对应的内存控制显示的内容, 它在内存中被映射到0xb80000, 我们每次写两个Byte, 第一个Byte是要显示的字符的ASCII码, 第二个Byte是字符的颜色和背景颜色, 然后就可以显示在屏幕上了, 向0xb8000开始的不同地址写入不同的内容会在屏幕的不同坐标出显示相应的内容, 这里给出显示满屏的@符号的代码

 

完整的代码

实现到此,我们这一次的任务就完成了, 完整的代码在github的repo可以获得, commit id 为  7e6abac774439e0c234a1fe5a40755f609db780a  执行make boot即可看到效果哦~

欢迎大家在评论区对文章进行吐槽或者评价, 这样我就能和大家交流学到更多的东西了~ 不是很开心的事情嘛

[科普向?] Re: 从零开始的操作系统开发 第一集

[科普向?] Re: 从零开始的操作系统开发 第一集

Hmm, 果然还是开坑了~! 在学校智障的操作系统课设的发起下, 再加上每个程序员都有一个写一个自己的操作系统的公主梦(雾), 我们愉(作)快(死)地开坑啦~

以前曾经跟着 “30天自制操作系统” 玩过DIY操作系统, 不过那个书更像面向小白, 讲的东西也不够系统, 而且使用的是自己改过的nas汇编器, 因而不能算写过, 这一次则是真正的开坑啦~ (虽然课设时间很短写出一个完整的根本不可能不过慢慢写总会写完的你说对不喵~)

我们的开发过程在Bearychat上直播 neugeek.bearychat.com 的Toy-OS频道, 我们的git-repo 为 https://github.com/VOID001/toy-os 菊苣们不要喷, 既然挖了坑窝就不会不填(….你都已经挖了多少个坑了啊喂! (逃))

这个系列的文章将会记录在开发操作系统的整个过程中的一些经验&心得&吐槽 不知道会有多少集(

 

参考资料们:

  • MIT 的 XV6 源代码 & handbook
  • University of Birmingham 的 Writing an simple operating system from scratch
  • Quora, StackOverflow
  • Jiong Zhao Linux 0.11内核完全注释

 

我们使用的工具链:

  • GNU Assembler & GNU C Compiler
  • Qemu
  • Gdb
  • objcopy, objdump, binutils, elfutils
  • GNU Makefile

 

操作系统编写总览

什么是BIOS

参考阅读: http://whatis.techtarget.com/definition/BIOS-basic-input-output-system

我们的定位是写一个操作系统,那么首先我们应该了解,整个操作系统都应该由哪些模块构成, 那么就让我们从操作系统的启动说起, 说到这里就不得不说一下BIOS, BIOS是Basic Input and Output System, 是你的计算机加电运行后加载的第一个程序, 它是固化在你的EPROM内的一个程序片段

BIOS被加载之后, CPU便会去执行BIOS的代码,这时候, BIOS进行硬件自检, 保证硬件没有故障后, 就会加载操作系统, 同时BIOS也提供了一个通用的接口, 供我们用来与不同的外设如VGA显示器进行交互, 具体如何使用将会在下文中介绍

boot-sector

刚刚我们说到了, BIOS作为开机运行的第一个程序, 在进行硬件自检后, 便会装载操作系统, 可是这时候,操作系统还在磁盘(或者其他存储介质内), BIOS如何知道, 我们知道BIOS是由厂商写死在ROM上的, 我们的操作系统程序如果每次存放的位置都不一样的话, 岂不是每次都要去重新刷写EPROM? 当然没那么麻烦, BIOS和编写操作系统的程序员有一个约定, 那就是, 当自检完毕之后, BIOS会自动按顺序(你设定的Boot Sequence)检查每一个media的第一个扇区(0扇区)是不是Bootable,如果找到一个Bootable的扇区, 那么就加载这个扇区到内存中, 接下来会执行这个刚刚装入内存的程序, 这样, 我们就可以在这里执行对硬件初始化&装载操作系统程序等操作啦

对于CPU而言, 代码和数据都是二进制,那么如何区分这是一个bootable扇区呢? 流我们将bootable扇区称为 boot-sector,  为了让CPU能够识别这个扇区是boot-sector, 对boot-sector有如下的要求:

  •  必须是512Byte大小
  • 512Byte的末尾两个Byte应该被填充为0xaa55

拥有了这两个条件, 这个扇区才是一个boot-sector, BIOS才会去加载它, 下面是一个非常简单的,开机后就让CPU进入死循环的一个程序的binary文件, 这就是一个boot-sector

中间的0被省略,  因为little endian的原因 0xaa55 在实际存储的时候为 55 aa

切换到32bit-protected mode

上面我们的所有操作都是在16bit 实地址模式(即你访问的地址就是真实的物理地址)下进行的, 而 16bit的实地址模式可以访问的内存最大为 1.0615234375(数字是如何计算出来的, 参考内存分段管理的相关知识 0xffff * 16 + 0xffff) 只比 1MB多一点的空间, 这对于我们之后要写的操作系统, 以及我们要运行的程序是远远不够的, 那么接下来我们就要切换到32bit的虚拟地址模式下, 进行接下来的开发

操作系统的核心模块

在切换到32bit protect mode之后, 我们需要实现的是kernel system call, file system, multiprocess scheduling, 以及 支持我们的Keyboard和VGA Driver

其他(Misc)

为了让我们的操作系统可以交互, 我们需要实现一个Interactive Shell, 并且实现几个能够运行在我们的操作系统上的程序, 之后也许还会支持网络 & 图形界面, SDL Driver等

 

以上就是一个综述啦, 可以看出来这是一个不小的坑, 不过嘛, 很有趣对吧~!

 

我们的第一个Hello world 操作系统

AT&T汇编+GNU Assembler的一些比较坑爹的事

为了更好的和XV6产生一致性, 我们采用了 AT&T 汇编, 使用的为GNU Assembler 进行开发, 关于AT&T汇编与Intel汇编的区别, 这里有参考文章http://www.delorie.com/djgpp/doc/brennan/brennan_att_inline_djgpp.html

需要注意的问题是, 之前在使用nasm作为Assembler的时候可以直接通过 -f bin 指定输出的程序为 RAW格式的, 即为不含有任何ELF(Linux下的文件格式)的信息, 而使用as进行编译的时候, 目前我们还没有找到办法直接输出RAW binary文件, 而且, 在AT&T中貌似也没有可以方便的在0扇区的最末尾填充0xaa55的方法(Intel中我们通常使用 time 510-($-$$) db 0  来在整个扇区填满0,  之后 dw 0xaa55 ), 另外! 另外! 对于那些在Intel下的标号后定义的字符串, nasm可以轻松识别地址, 然而, 然而, 然而!(重要的事情说三遍)as 不会! as会认为那是一个外部符号, 等待链接的时候Relocate, 因而下面这段代码如果不进行链接的话, 这个’string’ 标号就会被用 0x0替换

导致结果出错

 

那么正确的姿势是什么呢, 下面这样可以生成一个正确的RAW binary image(假设我们的源文件叫做boot.s)

 

计算机刚刚启动之后的内存布局

上面的代码中的0x7c00 是什么鬼肯定有人要问, 我们下面就来解释一下, 在计算机启动完毕之后, 内存的布局如下:

 

Screenshot from 2016-07-05 20-06-57

(本图摘自Writing a Simple Operating System — from Scratch)

我们可以看到, 在低地址最下方有着中断向量表, 再上方就是BIOS的数据内容, 然后, 为了防止我们的boot-sector将BIOS Data/中断向量表的内容覆盖, BIOS将boot-sector加载到的地址为0x7c00处, 那么现在来解释一下为什么链接的时候要指定这个参数, 因为当你链接的时候, 那些需要Relocate的符号, 是按照一个实际地址 + 在本文件内的偏移量给定的, 而因为我们的boot-sector默认加载的位置为0x7c00, 那么我们的实际地址就应该为0x7c00, 这时候我们再想指定某个标签(如string) 就会在运行的时候将string Relocate到 0x7c00 + 在binary文件中的offset(可以通过hexdump看到)

Qemu with GDB

为了让我们的调试更愉.悦, 能够使用gdb对代码进行调试则是极好的 qemu支持远程调试功能, 在运行qemu的时候, 指定参数 -s(开启1234端口并且等待debugger链接) -S(先不要执行CPU指令) 后, 即可通过gdb连接到这个端口进行调试啦, 具体的操作方法如下

有了这个之后, 在操作系统的开发初期就能更好的看~代~码~啦

Writing our first Hello world OS

为了表示对LL的敬意, 我们准备在屏幕上输出 Hello Niconico, 之前已经说过了, BIOS提供了一部分通用的接口供我们和硬件打交道, 我们就不需要关心硬件的更具体的细节了, 这里我们就要用到这个接口, BIOS 将此接口通过中断的形式提供给我们. 为了在屏幕上输出一个字符, 我们通过给一些特定的寄存器赋值, 即汇编语言的参数传递, 类似C语言的参数传递. 一个简短的打印一个 字符’A’的代码如下:

我们为了实现打印一整串字符串,  一个稍微复杂点的程序如下:

这里注意, 我们被AT&T的汇编坑了好久的一个地方就是 mov $string, %bx . 刚开始, 我们写的是

mov string, %bx  这个代码一直没有办法打印出我们想要的字符串, 原因就是, 在ATT汇编中, 这句被解释为了, 将string 这个标号处对应的内容取来, 放到%bx中, 而我们想要实现的是: 将string这个标号对应的地址取来,放到bx中, 如果不加$, 所有的地址在ATT中都会被解释为”那个地方的内容” 一定要小心

以上代码可以在github获取到~ 这个版本对应的commit是  8c2f90aa8830edaf9ea10809797d14918efb463e  只要按照README装好需要的工具, 执行  make boot 就可以看到效果啦~

 

FisKyZcAmN3juq8lmTlENUyJ55tF

 

今天就到这里啦

用C语言操作lua

用C语言操作lua

lua提供了一套C语言的API供用户使用C语言与lua进行交互, 本文通过解读lua C Application Program Interface并且提供具体的实例来表明如何使用C操作lua, 本文以lua5.1为例, lua5.3支持了一部分新的特性不过都大同小异

Lua Stack

对于C, Lua之间的交互, 很自然的会想到如何获得lua的数据,如何将处理的结果返回给lua, lua使用一个Virtual Stack将其函数调用的参数传递给C 如下所示 加入我们有一个lua的调用 fun(a, b, c) 这个调用会将 a, b, c三个参数依次压栈, 在Virtual Stack中形成这样的结构:

为了方便用户取用函数的任意参数, lua的 Virtual Stack不仅允许你访问栈顶元素, 同时允许访问栈内任何位置的元素,  lua的API均有一个参数为index, 用于指定对栈上的哪一个参数进行操作, 最后, C语言处理之后的结果要返回给lua, 也是通过压栈的形式, 将结果压栈, 因为lua支持多个返回值, C语言通过返回一个int, 表明将自栈顶算起的多少参数作为调用该lua函数的返回值, 其他的参数均被丢弃掉. 下面通过几个小的demo来说明一下如何通过C语言对lua进行操作. 我们使用lua_sandbox来演示如何用C语言实现lua的函数.

获取用户的每一个传递的参数

首先介绍一下如何实现一个C语言函数并且可以导出到lua_sandbox中, 我们需要做两个工作, 第一个是实现这个函数, 函数的实现标准如下:

然后使用

将该函数添加到你要运行的lua_sandbox实例中, 即可在lua代码内调用你写的函数啦

 

下面我们实现这个函数:

然后我们将函数export为 get_args 在lua中调用该函数

就会依次输出这几个参数, 下面我们进行一个稍微复杂点的例子, 遍历输出lua的table

输出lua的table

C语言中是没有map, 这种结构的, 即key-value存储结构, C++通过STL对map实现了支持, 而lua则原生支持map, lua支持一种叫做table的数据结构, 为key-value对, 而且一个table内可以存储多种不同类型的数据, 为了让C语言也能访问lua的table结构,lua API提供了 lua_next 这个函数

lua_next首先将Virtual Stack中的一个参数pop出栈, 然后将index所指定的位置的表格内的下一个元素的key, value依次压栈, 即value在栈顶, key 在次栈顶, 将整个表格遍历完毕之后, lua_next将会返回 NULL, 此时终止遍历即可 ,下面是遍历实现的代码

 

 

 

*  一个警告: 在一个迭代中,小心使用index=-1指定lua栈顶元素, 除非你确定你的确是要指向栈顶元素,而不是要指向当前的栈顶元素, 这么说可能有点绕,我举个例子来说明

我们看一个精简版的visit_table的例子

如果这个table就在栈顶的话, 我们可不可以将t换成 -1呢?指向 栈顶的table? 答案是不可以, 如果换成了-1 那么 lua_next第一次执行之后因为取出了key-value对,目前栈顶的元素发生了改变,变成了这个table内此次迭代的, 而 index = -1还会去取栈顶的元素, 这时候lua_next访问的table就不是你想要访问的那个table了,  而是这个table内的某个value, lua_next就很可能无法继续下去了(说很可能无法继续下去的原因,是因为如果这个value是一个table的话, 那么lua_next还可以继续下去,只不过不可能得到正确的结果), 那么这种情况下如果想指定当前栈顶的元素为lua_next操作的table的话,应该这样指定: