内容简介:原文链接:http://www.liankuai.tech/public/technology/108.html
原文链接:http://www.liankuai.tech/public/technology/108.html
1
目录
☞概念
☞语法
☞操作码
☞字面量
☞函数风格
☞访问外部函数与变量
☞标签
☞定义局部变量
☞赋值
☞Switch
☞循环
☞函数
☞内联汇编中注意事项
☞Solidity中的惯例
2
概念
通常我们通过库代码,来增强语言,实现一些精细化的控制,Solidity为我们提供了一种接近于EVM底层的语言,内联汇编,允许与Solidity结合使用。由于EVM是栈式的,所以有时定位栈比较麻烦,Solidty的内联汇编为我们提供了下述的特性,来解决手写底层代码带来的各种问题:
• 允许函数风格的操作码:mul(1, add(2, 3))等同于push1 3 push1 2 add push1 1 mul
• 内联局部变量:let x := add(2, 3) let y := mload(0x40) x := add(x, y)
• 可访问外部变量:function f(uint x) { assembly { x := sub(x, 1) } }
• 标签:let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))
• 循环:for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }
• switch语句:switch x case 0 { y := mul(x, 2) } default { y := 0 }
• 函数调用:function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) } }
需要注意的是内联汇编是一种非常底层的方式来访问EVM虚拟机。他没有Solidity提供的多种安全机制。
2.1 示例
下面的例子提供了一个库函数来访问另一个合约,并把它写入到一个bytes变量中。有一些不能通过常规的Solidity语言完成,内联库可以用来在某些方面增强语言的能力。
内联汇编在当编译器没办法得到有效率的代码时非常有用。但需要留意的是内联汇编语言写起来是比较难的,因为编译器不会进行一些检查,所以你应该只在复杂的,且你知道你在做什么的事情上使用它。
3
语法
内联汇编语言也会像Solidity一样解析注释,字面量和标识符。所以你可以使用//和/**/的方式注释。内联汇编的在Solidity中的语法是包裹在assembly { ... },下面是可用的语法,更详细的语法请参考官方API https://solidity.readthedocs.io/en/v0.4.21/assembly.html。
• 字面量。如0x123,42或abc(字符串最多是32个字符)
• 操作码(指令的方式),如mload sload dup1 sstore,后面有可• 支持的指令列表
• 函数风格的操作码,如add(1, mlod(0))
• 标签,如name:
• 变量定义,如let x := 7 或 let x := add(y, 3)
• 标识符(标签或内联局部变量或外部),如jump(name),3 x add
• 赋值(指令风格),如,3 =: x。
• 函数风格的赋值,如x := add(y, 3)
• 支持块级的局部变量,如{ let x := 3 { let y := add(x, 1) } }
4
操作码
如果一个操作码有参数(通过在栈顶),那么他们会放在括号。需要注意的是参数的顺序可以颠倒(非函数风格,后面会详细说明)。用-标记的操作码不会将一个参数推到栈顶,而标记为*的是非常特殊的,所有其它的将且只将一个推到栈顶。
在后面的例子中,mem[a...b)表示成位置a到位置b(不包含)的memory字节内容,storage[p]表示在位置p的strorage内容。
操作码pushi和jumpdest不能被直接使用。在语法中,操作码被表示为预先定义的标识符。
5
字面常量
你可以直接键入十进制或十六进制符号来作为整型常量使用,这会自动生成相应的 PUSHi 指令。 下面的代码将计算 2 加 3(等于 5),然后计算其与字符串 “abc” 的按位与。字符串在存储时为左对齐,且长度不能超过 32 字节。
6
函数风格
你可以像使用字节码那样在操作码之后键入操作码。例如,把 3 与内存位置 0x80 处的数据相加就是:
由于通常很难看到某些操作码的实际参数是什么,所以 Solidity 内联汇编还提供了一种“函数风格”表示法,同样功能的代码可以写做:
函数风格表达式内不能使用指令风格的写法,即 1 2 mstore(0x80, add) 是无效汇编语句, 它必须写成 mstore(0x80, add(2, 1)) 这种形式。对于不带参数的操作码,括号可以省略。
注意,在函数风格写法中参数的顺序与指令风格相反。如果使用函数风格写法,第一个参数将会位于栈顶。
7
访问外部变量和函数
通过简单使用它们名称就可以访问 Solidity 变量和其他标识符。对于内存变量,这会将地址而不是值压入栈中。 存储变量是不同的,因为存储变量的值可能不占用完整的存储槽,因此其“地址”由存储槽和槽内的字节偏移量组成。 为了获取变量 x 所使用的存储槽,你可以使用 x_slot,并用的 x_offset 获取其字节偏移量。
在赋值语句中(见下文),我们甚至可以使用 Solidity 局部变量来赋值。
对于内联汇编而言的外部函数也可以被访问:汇编会将它们的入口标签(带有虚拟函数解析)压入栈中。Solidity 中的调用语义为:
• 调用者压入 return label、arg1、arg2、...、argn
• 被调用方返回 ret1、ret2、...、retm
这个特性使用起来还是有点麻烦,因为在调用过程中堆栈偏移量发生了根本变化,因此对局部变量的引用将会出错。
8
标签
EVM 汇编的另一个问题是 jump 和 jumpi 函数使用绝对地址,这些绝对地址很容易改变。 Solidity 内联汇编提供了标签,以便更容易地使用 jump。注意,标签具有底层特征,使用循环、if 和 switch 指令(参见下文)而不使用标签也能写出高效汇编代码。 以下代码用来计算斐波那契数列中的一个元素。
请注意:只有汇编程序知道当前栈高度时,才能自动访问堆栈变量。如果 jump 源和目标的栈高度不同,访问将失败。 虽然我们可以这么使用 jump,但在这种情况下,你不应该去访问任何栈里的变量(即使是汇编变量)。
此外,栈高度分析器还可以通过操作码(而不是根据控制流)检查代码操作码,因此在下面的情况下,汇编程序对标签 two 处的堆栈高度会产生错误的印象:
9
汇编局部变量声明
你可以使用 let 关键字来声明只在内联汇编中可见的变量,实际上只在当前的 {...} 块中可见。 下面发生的事情应该是:let 指令将创建一个为变量保留的新数据槽,并在到达块末尾时自动删除。 你需要为变量提供一个初始值,它可以只是 0,但它也可以是一个复杂的函数风格表达式。
10
赋值
可以给汇编局部变量和函数局部变量赋值。请注意:当给指向内存或存储的变量赋值时,你只是更改指针而不是数据。
有两种赋值方式:函数风格和指令风格。对于函数风格赋值(变量 := 值),你需要在函数风格表达式中提供一个值,它恰好可以产生一个栈里的值; 对于指令风格赋值(=: 变量),则仅从栈顶部获取数据。对于这两种方式,冒号均指向变量名称。赋值则是通过用新值替换栈中的变量值来实现的。
11
If
if 语句可以用于有条件地执行代码,且没有“else”部分;如果需要多种选择,你可以考虑使用“switch”(见下文)。
12
Swicth
作为“if/else”的非常初级的版本,你可以使用 switch 语句。它计算表达式的值并与几个常量进行比较。选出与匹配常数对应的分支。 与某些编程语言容易出错的情况不同,控制流不会从一种情形继续执行到下一种情形。我们可以设定一个 fallback 或称为 default 的默认情况。
13
循环
汇编语言支持一个简单的 for-style 循环。For-style 循环有一个头,它包含初始化部分、条件和迭代后处理部分。 条件必须是函数风格表达式,而另外两个部分都是语句块。如果起始部分声明了某个变量,这些变量的作用域将扩展到循环体中(包括条件和迭代后处理部分)。
下面例子是计算某个内存区域中的数值总和。
For 循环也可以写成像 while 循环一样:只需将初始化部分和迭代后处理两部分留空。
14
函数
汇编语言允许定义底层函数。底层函数需要从栈中取得它们的参数(和返回 PC),并将结果放入栈中。调用函数的方式与执行函数风格操作码相同。
函数可以在任何地方定义,并且在声明它们的语句块中可见。函数内部不能访问在函数之外定义的局部变量。这里没有严格的 return 语句。
如果调用会返回多个值的函数,则必须使用 a,b:= f(x) 或 let a,b:= f(x) 的方式把它们赋值到一个元组。
下面例子通过平方和乘法实现了幂运算函数。
15
注意事项
内联汇编语言可能具有相当高级的外观,但实际上它是非常低级的编程语言。函数调用、循环、if 语句和 switch 语句通过简单的重写规则进行转换, 然后,汇编程序为你做的唯一事情就是重新组织函数风格操作码、管理 jump 标签、计算访问变量的栈高度,还有在到达语句块末尾时删除局部汇编变量的栈数据。 特别是对于最后两种情况,汇编程序仅会按照代码的顺序计算栈的高度,而不一定遵循控制流程;了解这一点非常重要。此外,swap 等操作只会交换栈内的数据,而不是变量位置。
16
Solidity惯例
与 EVM 汇编语言相比,Solidity 能够识别小于 256 位的类型,例如 uint24。为了提高效率,大多数算术运算只将它们视为 256 位数字, 仅在必要时才清除未使用的数据位,即在将它们写入内存或执行比较之前才会这么做。这意味着,如果从内联汇编中访问这样的变量,你必须先手工清除那些未使用的数据位。
Solidity 以一种非常简单的方式管理内存:在 0x40 的位置有一个“空闲内存指针”。如果你打算分配内存,只需从此处开始使用内存,然后相应地更新指针即可。
内存的开头 64 字节可以用来作为临时分配的“暂存空间”。“空闲内存指针”之后的 32 字节位置(即从 0x60 开始的位置)将永远为 0,可以用来初始化空的动态内存数组。
在 Solidity 中,内存数组的元素总是占用 32 个字节的倍数(是的,甚至对于 byte[] 都是这样,只有 bytes 和 string 不是这样)。 多维内存数组就是指向内存数组的指针。动态数组的长度存储在数组的第一个槽中,其后才是数组元素。
-END-
在职学习区块链,转行区块链工程师
-月薪30-50k-
现在报名面授班即可减免学费1000元
(优惠截止10月24日)
清华、牛津、中科院等博士与专家研发
精品小班面授课,与老师面对面交流
半年可免费复训,永久社群答疑服务
来源:链块学院
本文由布洛克专栏作者发布,代表作者观点,版权归作者所有,不代表布洛克科技观点
——TheEnd——
关注“布洛克科技”
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- iframe内联框架之巧妙跨域
- 提升go编译器内联程度
- 重学前端:块级元素与内联元素
- 内联第三方依赖到自己的包中
- [译] Go语言inline内联的策略与限制
- [译] Go 语言 inline 内联的策略与限制
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
21天学通C语言
(美国)琼斯(Bradley L.Jones) (美国)埃特肯(Peter Aitken) / 信达工作室 / 人民邮电出版社 / 2012-8 / 69.00元
《21天学通C语言(第6版•修订版)》是初学者学习C语言的经典教程。本版按最新的标准(ISO∕IEC:9899-1999),以循序渐进的方式介绍了C语言编程方面知识,并提供了丰富的实例和大量的练习。通过学习实例,并将所学的知识用于完成练习,读者将逐步了解、熟悉并精通C语言。《21天学通C语言(第6版•修订版)》包括四周的课程。第一周的课程介绍了C语言程序的基本元素,包括变量、常量、语句、表达式、函......一起来看看 《21天学通C语言》 这本书的介绍吧!