0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

内联汇编很可难吗 看完这篇文章就能搞定!

Q4MP_gh_c472c21 来源:IOT物联网小镇 作者:道哥 2021-04-29 13:57 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

一、基本 asm 格式

1. 语法规则

2.test1.c 插入空指令

3. test2.c操作全局变量

4. test3.c 尝试操作局部变量

二、扩展 asm 格式

1. 指令格式

2.输出和输入操作数列表

3.test4.c通过寄存器操作局部变量

4. test5.c声明使用的寄存器

三、使用占位符来代替寄存器名称

1. test6.c使用占位符代替寄存器名

2. test7.c 给寄存器起别名

四、使用内存地址

1.test8.c 使用内存地址来操作数据

五、总结

Linux 代码中,经常可以看到在 C 代码中,嵌入部分汇编代码,这些代码要么是与硬件体系相关的,要么是对性能有关键影响的。

在很久以前,我特别惧怕内嵌汇编代码,直到后来把汇编部分的短板补上之后,才彻底终结这种心理。

也许你在工作中,几乎不会涉及到内嵌汇编代码的工作,但是一旦进入到系统的底层,或者需要对时间关键场景进行优化,这个时候你的知识储备就发挥重要作用了!

这篇文章,我们就来详细聊一聊在 C 语言中,如何通过asm 关键字来嵌入汇编语言代码,文中的 8 个示例代码从简单到复杂,逐步深入地介绍内联汇编的关键语法规则。

希望这篇文章能够成为你进阶高手路上的垫脚石!

PS:

示例代码中使用的是 Linux 系统中 AT&T 汇编语法;

文章中的 8 个示例代码,可以在公众号后台回复【426】,即可收到下载地址;

一、基本 asm 格式

gcc 编译器支持2 种形式的内联 asm 代码:

基本 asm 格式:不支持操作数;

扩展 asm 格式:支持操作数;

1. 语法规则

asm [volatile] ("汇编指令")

所有指令,必须用双引号包裹起来;

超过一条指令,必须用 分隔符进行分割,为了排版,一般会加上 ;

多条汇编指令,可以写在一行,也可以写在多行;

关键字 asm 可以使用asm来替换;

volatile 是可选的,编译器有可能对汇编代码进行优化,使用 volatile 关键字之后,告诉编译器不要优化手写的内联汇编代码。

2. test1.c 插入空指令

#include 
int main()
{
    asm ("nop");
    printf("hello
");
    asm ("nop
	nop
	"
 "nop");
    return 0;
}

注意:C语言中会自动把两个连续的字符串字面量拼接成一个,所以"nop nop " "nop"这两个字符串会自动拼接成一个字符串。

生成汇编代码指令:

gcc -m32 -S -o test1.s test1.c

test1.s 中内容如下(只贴出了内联汇编代码相关部分的代码):

#APP
# 5 "test1.c" 1
nop
# 0 "" 2
#NO_APP
// 这里是 printf 语句生成的代码。
#APP
# 7 "test1.c" 1
nop
nop
nop
# 0 "" 2
#NO_APP

可以看到,内联汇编代码被两个注释(#APP ... #NO_APP)包裹起来。在源码中嵌入了两个汇编代码,因此可以看到 gcc 编译器生成的汇编代码中包含了这两部分代码。

这 2 部分嵌入的汇编代码都是空指令 nop,没有什么意义。

3. test2.c 操作全局变量

在 C 代码中嵌入汇编指令,目的是用来计算,或者执行一定的功能,下面我们就来看一下,如何在内联汇编指令中,操作全局变量。

#include 

int a = 1;
int b = 2;
int c;

int main()
{
    asm volatile ("movl a, %eax
	"
        "addl b, %eax
	"
        "movl %eax, c");
    printf("c = %d 
", c);
    return 0;
}

关于汇编指令中编译器的基本知识:

eax, ebx 都是 x86 平台中的寄存器(32位),在基本asm格式中,寄存器的前面必须加上百分号%。

32 位的寄存器eax可以当做 16 位来使用(ax),或者当做 8 位来使用(ah, al),本文只会按照 32 位来使用。

代码说明:

movl a, %eax // 把变量a的值复制到 %eax 寄存器中;

addl b, %eax // 把变量 b 的值 与 %eax 寄存器中的值(a)相加,结果放在 %eax 寄存器中;

movl %eax, c // 把 %eax 寄存器中的值复制到变量 c 中;

3f087b10-a86f-11eb-9728-12bb97331649.png

生成汇编代码指令:

gcc -m32 -S -o test2.s test2.c

test2.s 内容如下(只贴出与内联汇编代码相关部分):

#APP

# 9 "test2.c" 1
movl a, %eax
addl b, %eax
movl %eax, c
# 0 "" 2
#NO_APP

可以看到,在内联汇编代码中,可以直接使用全局变量 a, b 的名称来操作。执行 test2,可以得到正确的结果。

思考一个问题:为什么在汇编代码中,可以使用变量a, b, c?

查看 test2.s 中内联汇编代码之前的部分,可以看到:

.file"test2.c"
.globla
.data
.align 4
.typea, @object
.sizea, 4
a:
.long1
.globlb
.align 4
.typeb, @object
.sizeb, 4
b:
.long2
.commc,4,4

变量 a, b 被 .globl 修饰,c 被 .comm 修饰,相当于是把它们导出为全局的,所以可以在汇编代码中使用。

那么问题来了:如果是一个局部变量,在汇编代代码中就不会用 .globl 导出,此时在内联汇编指令中,还可以直接使用吗?

眼见为实,我们把这 3 个变量放到 main 函数的内部,作为局部变量来试一下。

4. test3.c 尝试操作局部变量

#include 
int main()
{
    int a = 1;
    int b = 2;
    int c;

    asm("movl a, %eax
	"
        "addl b, %eax
	"
        "movl %eax, c");
    printf("c = %d 
", c);
    return 0;
}

生成汇编代码指令:

gcc -m32 -S -o test3.s test3.c

在 test3.s 中可以看到没有a, b, c 的导出符号,a 和 b 没有其他地方使用,因此直接把他们的数值复制到栈空间中了:

movl$1, -20(%ebp)
movl$2, -16(%ebp)

40e39028-a86f-11eb-9728-12bb97331649.png

我们来尝试编译成可执行程序:

$ gcc -m32 -o test3 test3.c
/tmp/ccuY0TOB.o: In function `main':
test3.c undefined reference to `a'
test3.c undefined reference to `b'
test3.c undefined reference to `c'
collect2: error: ld returned 1 exit status

编译报错:找不到对 a,b,c 的引用!那该怎么办,才能使用局部变量呢?扩展 asm 格式!

二、扩展 asm 格式

1. 指令格式

asm [volatile] ("汇编指令" : "输出操作数列表" : "输入操作数列表" : "改动的寄存器")

格式说明

汇编指令:与基本asm格式相同;

输出操作数列表:汇编代码如何把处理结果传递到 C 代码中;

输入操作数列表:C 代码如何把数据传递给内联汇编代码;

改动的寄存器:告诉编译器,在内联汇编代码中,我们使用了哪些寄存器;

“改动的寄存器”可以省略,此时最后一个冒号可以不要,但是前面的冒号必须保留,即使输出/输入操作数列表为空。

关于“改动的寄存器”再解释一下:gcc 在编译 C 代码的时候,需要使用一系列寄存器;我们手写的内联汇编代码中,也使用了一些寄存器。

为了通知编译器,让它知道:在内联汇编代码中有哪些寄存器被我们用户使用了,可以在这里列举出来,这样的话,gcc 就会避免使用这些列举出的寄存器

2. 输出和输入操作数列表的格式

在系统中,存储变量的地方就2个:寄存器和内存。因此,告诉内联汇编代码输出和输入操作数,其实就是告诉它:

向哪些寄存器或内存地址输出结果;

从哪些寄存器或内存地址读取输入数据;

这个过程也要满足一定的格式:

"[输出修饰符]约束"(寄存器或内存地址)

(1)约束

就是通过不同的字符,来告诉编译器使用哪些寄存器,或者内存地址。包括下面这些字符:

a: 使用 eax/ax/al 寄存器;

b: 使用 ebx/bx/bl 寄存器;

c: 使用 ecx/cx/cl 寄存器;

d: 使用 edx/dx/dl 寄存器;

r: 使用任何可用的通用寄存器;

m: 使用变量的内存位置;

先记住这几个就够用了,其他的约束选项还有:D, S, q, A, f, t, u等等,需要的时候再查看文档。

(2)输出修饰符

顾名思义,它使用来修饰输出的,对输出寄存器或内存地址提供额外的说明,包括下面4个修饰符:

+:被修饰的操作数可以读取,可以写入;

=:被修饰的操作数只能写入;

%:被修饰的操作数可以和下一个操作数互换;

&:在内联函数完成之前,可以删除或者重新使用被修饰的操作数;

语言描述比较抽象,直接看例子!

3. test4.c 通过寄存器操作局部变量

#include 

int main()
{
    int data1 = 1;
    int data2 = 2;
    int data3;

    asm("movl %%ebx, %%eax
	"
        "addl %%ecx, %%eax"
        : "=a"(data3)
        : "b"(data1),"c"(data2));

    printf("data3 = %d 
", data3);
    return 0;
}

有 2 个地方需要注意一下啊:

在内联汇编代码中,没有声明“改动的寄存器”列表,也就是说可以省略掉(前面的冒号也不需要);

扩展asm格式中,寄存器前面必须写 2 个%;

代码解释:

"b"(data1),"c"(data2) ==> 把变量 data1 复制到寄存器 %ebx,变量 data2 复制到寄存器 %ecx。这样,内联汇编代码中,就可以通过这两个寄存器来操作这两个数了;

"=a"(data3) ==> 把处理结果放在寄存器 %eax 中,然后复制给变量data3。前面的修饰符等号意思是:会写入往 %eax 中写入数据,不会从中读取数据;

通过上面的这种格式,内联汇编代码中,就可以使用指定的寄存器来操作局部变量了,稍后将会看到局部变量是如何从经过栈空间,复制到寄存器中的。

生成汇编代码指令:

gcc -m32 -S -o test4.s test4.c

汇编代码 test4.s 如下:

movl$1, -20(%ebp)
movl$2, -16(%ebp)
movl-20(%ebp), %eax
movl-16(%ebp), %edx
movl%eax, %ebx
movl%edx, %ecx
#APP
# 10 "test4.c" 1
movl %ebx, %eax
addl %ecx, %eax
# 0 "" 2
#NO_APP
    movl%eax, -12(%ebp)

411b1994-a86f-11eb-9728-12bb97331649.png

可以看到,在进入手写的内联汇编代码之前:

把数字 1 通过栈空间(-20(%ebp)),复制到寄存器 %eax,再复制到寄存器 %ebx;

把数字 2 通过栈空间(-16(%ebp)),复制到寄存器 %edx,再复制到寄存器 %ecx;

这 2 个操作正是对应了内联汇编代码中的“输入操作数列表”部分:"b"(data1),"c"(data2)。

在内联汇编代码之后(#NO_APP 之后),把%eax寄存器中的值复制到栈中的-12(%ebp)位置,这个位置正是局部变量 data3所在的位置,这样就完成了输出操作。

4. test5.c 声明改动的寄存器

在 test4.c 中,我们没有声明改动的寄存器,所以编译器可以任意选择使用哪些寄存器。从生成的汇编代码 test4.s 中可以看到,gcc 使用了%edx寄存器。

那么我们来测试一下:告诉 gcc 不要使用 %edx 寄存器。

#include 
int main()
{
    int data1 = 1;
    int data2 = 2;
    int data3;

    asm("movl %%ebx, %%eax
	"
        "addl %%ecx, %%eax"
        : "=a"(data3)
        : "b"(data1),"c"(data2)
        : "%edx");

    printf("data3 = %d 
", data3);
    return 0;
}

代码中,asm 指令最后部分"%edx",就是用来告诉 gcc 编译器:在内联汇编代码中,我们会使用到 %edx 寄存器,你就不要用它了。

生成汇编代码指令:

gcc -m32 -S -o test5.s test5.c

来看一下生成的汇编代码 test5.s:

    movl$1, -20(%ebp)
movl$2, -16(%ebp)
movl-20(%ebp), %eax
movl-16(%ebp), %ecx
movl%eax, %ebx
#APP
# 10 "test5.c" 1
movl %ebx, %eax
addl %ecx, %eax
# 0 "" 2
#NO_APP
movl%eax, -12(%ebp)

415f4d94-a86f-11eb-9728-12bb97331649.png

可以看到,在内联汇编代码之前,gcc没有选择使用寄存器%edx。

三、使用占位符来代替寄存器名称

在上面的示例中,只使用了2 个寄存器来操作 2 个局部变量,如果操作数有很多,那么在内联汇编代码中去写每个寄存器的名称,就显得很不方便。

因此,扩展 asm 格式为我们提供了另一种偷懒的方法,来使用输出和输入操作数列表中的寄存器:占位符!

占位符有点类似于批处理脚本中,利用2...来引用输入参数一样,内联汇编代码中的占位符,从输出操作数列表中的寄存器开始从0编号,一直编号到输入操作数列表中的所有寄存器。

还是看例子比较直接!

1. test6.c 使用占位符代替寄存器

#include 
int main()
{
    int data1 = 1;
    int data2 = 2;
    int data3;

    asm("addl %1, %2
	"
        "movl %2, %0"
        : "=r"(data3)
        : "r"(data1),"r"(data2));

    printf("data3 = %d 
", data3);
    return 0;
}

代码说明:

输出操作数列表"=r"(data3):约束使用字符 r, 也就是说不指定寄存器,由编译器来选择使用哪个寄存器来存储结果,最后复制到局部变量 data3中;

输入操作数列表"r"(data1),"r"(data2):约束字符r, 不指定寄存器,由编译器来选择使用哪 2 个寄存器来接收局部变量 data1 和 data2;

输出操作数列表中只需要一个寄存器,因此在内联汇编代码中的 %0 就代表这个寄存器(即:从 0 开始计数);

输入操作数列表中有 2 个寄存器,因此在内联汇编代码中的 %1 和 %2就代表这 2 个寄存器(即:从输出操作数列表的最后一个寄存器开始顺序计数);

生成汇编代码指令:

gcc -m32 -S -o test6.s test6.c

汇编代码如下 test6.s:

movl$1, -20(%ebp)
movl$2, -16(%ebp)
movl-20(%ebp), %eax
movl-16(%ebp), %edx
#APP
# 10 "test6.c" 1
addl %eax, %edx
movl %edx, %eax
# 0 "" 2
#NO_APP
movl%eax, -12(%ebp)

422d3ac4-a86f-11eb-9728-12bb97331649.png

可以看到,gcc 编译器选择了%eax来存储局部变量 data1,%edx来存储局部变量 data2,然后操作结果也存储在%eax 寄存器中。

是不是感觉这样操作就方便多了?不用我们来指定使用哪些寄存器,直接交给编译器来选择。

在内联汇编代码中,使用%0、%1 、%2这样的占位符来使用寄存器。

别急,如果您觉得使用编号还是麻烦,容易出错,还有另一个更方便的操作:扩展 asm 格式还允许给这些占位符重命名,也就是给每一个寄存器起一个别名,然后在内联汇编代码中使用别名来操作寄存器。

还是看代码!

2. test7.c 给寄存器起别名

#include 
int main()
{
    int data1 = 1;
    int data2 = 2;
    int data3;

    asm("addl %[v1], %[v2]
	"
        "movl %[v2], %[v3]"
        : [v3]"=r"(data3)
        : [v1]"r"(data1),[v2]"r"(data2));

    printf("data3 = %d 
", data3);
    return 0;
}

代码说明:

输出操作数列表:给寄存器(gcc 编译器选择的)取了一个别名 v3;

输入操作数列表:给寄存器(gcc 编译器选择的)取了一个别名 v1 和 v2;

起立别名之后,在内联汇编代码中就可以直接使用这些别名( %[v1], %[v2], %[v3])来操作数据了。

生成汇编代码指令:

gcc -m32 -S -o test7.s test7.c

再来看一下生成的汇编代码 test7.s:

movl$1, -20(%ebp)
movl$2, -16(%ebp)
movl-20(%ebp), %eax
movl-16(%ebp), %edx
#APP
# 10 "test7.c" 1
addl %eax, %edx
movl %edx, %eax
# 0 "" 2
#NO_APP
movl%eax, -12(%ebp)

这部分的汇编代码与 test6.s 中完全一样!

四、使用内存位置

在以上的示例中,输出操作数列表和输入操作数列表部分,使用的都是寄存器(约束字符:a, b, c, d, r等等)。

我们可以指定使用哪个寄存器,也可以交给编译器来选择使用哪些寄存器,通过寄存器来操作数据,速度会更快一些。

如果我们愿意的话,也可以直接使用变量的内存地址来操作变量,此时就需要使用约束字符 m。

1. test8.c 使用内存地址来操作数据

#include 
int main()
{
    int data1 = 1;
    int data2 = 2;
    int data3;

    asm("movl %1, %%eax
	"
        "addl %2, %%eax
	"
        "movl %%eax, %0"
        : "=m"(data3)
        : "m"(data1),"m"(data2));

    printf("data3 = %d 
", data3);
    return 0;
}

代码说明:

输出操作数列表 "=m"(data3):直接使用变量 data3 的内存地址;

输入操作数列表 "m"(data1),"m"(data2):直接使用变量 data1, data2 的内存地址;

在内联汇编代码中,因为需要进行相加计算,因此需要使用一个寄存器(%eax),计算这个环节是肯定需要寄存器的。

在操作那些内存地址中的数据时,使用的仍然是按顺序编号的占位符。

生成汇编代码指令:

gcc -m32 -S -o test8.s test8.c

生成的汇编代码如下 test8.s:

movl$1, -24(%ebp)
movl$2, -20(%ebp)
#APP
# 10 "test8.c" 1
movl -24(%ebp), %eax
addl -20(%ebp), %eax
movl %eax, -16(%ebp)
# 0 "" 2
#NO_APP
movl-16(%ebp), %eax

425d8d64-a86f-11eb-9728-12bb97331649.png

可以看到:在进入内联汇编代码之前,把data1 和 data2的值放在了栈中,然后直接把栈中的数据与寄存器%eax进行操作,最后再把操作结果(%eax),复制到栈中data3 的位置(-16(%ebp))。

五、总结

通过以上 8 个示例,我们把内联汇编代码中的关键语法规则进行了讲解,有了这个基础,就可以在内联汇编代码中编写更加复杂的指令了。

希望以上内容对您能有所帮助!谢谢!

编辑:jq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 寄存器
    +关注

    关注

    31

    文章

    5623

    浏览量

    130627
  • ASM
    ASM
    +关注

    关注

    1

    文章

    69

    浏览量

    19986
  • 代码
    +关注

    关注

    30

    文章

    4983

    浏览量

    74534
  • 编译器
    +关注

    关注

    1

    文章

    1673

    浏览量

    52071

原文标题:内联汇编很可怕吗?看完这篇文章,终结它!

文章出处:【微信号:gh_c472c2199c88,微信公众号:嵌入式微处理器】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    内联函数介绍

    内联函数禁用所有的编译选项。使用__inline修饰函数导致函数在调用处直接替换为函数体。这样代码调用函数更快,但增加代码的大小,特别在函数本身比较大而且经常调用的情况下。 __inline
    发表于 12-12 07:08

    inline ASM(内联汇编)的细节分享

    内联汇编 基本的语法 asm volatile (\"instruction list\":OUTPUT:INPUT:(clobber/modify)); 如果
    发表于 10-31 09:11

    C内联汇编nice指令解读

    一、指令基本结构 支持类型:只支持R型 NameopcodetypeCUSTOM07'h0bR type CUSTOM17'h2bR tpye CUSTOM27'h5bR type CUSTOM37'h7bR type1.costom3 lbuf(a1): 加载内存中的数据至row_buf : ​ .insn r opcode,func3,func7,rd,rs1,rs2 2.costom3 sbuf(a1): 保存数据row_buf的数据至内存: ​ .insn r opcode,func3,func7,rd,rs1,rs2 3.costom3 acc rowsum: 加载a1地址的数据,累加row 数据并写回: rowsum rd,a1,x0 ​ .insn r opcode,func3,func7,rd,rs1,rs2 二、示例分析 1.custom_lbuf ".insn r 0x7b, 2, 1, x0, %1, x0" :"=r"(zero) :"r"(addr) 字母“r”表示使用编译器自动分配的寄存器来存储该操作数变量;字母“m” 表示使用内存地址来存储该操作数变量。如果同时指明“rm”,则编译器自动选择最优方案。。 对于“输出操作数”而言,等号“=”代表输出变量用作输出,原来的值会被新值替换;“+”代表输出变量不仅作为输出,而且作为输入。 opcodefunc3func7rdrs1rs20x7b21x0%1x0 custom3只读取rs1的数据第一种操作零寄存器保存addr地址零寄存器 11110110100000001000000101000000 机器码: funct7rs2rs1funct3rdopcode00000010000001010010000001111011 把addr的地址加载到源寄存器1,编译器会自动分配rs1用哪一个寄存器(这里是x10),执行lbuf操作,将数据导入nice_core。 2.custom_sbuf ".insn r 0x7b, 2, 2, x0, %1, x0" :"=r"(zero) :"r"(addr) opcodefunc3func7rdrs1rs20x7b22x0%1x0 custom3只读取rs1的数据第二种操作零寄存器保存addr地址零寄存器 11110110100000010000000101000000机器码: funct7rs2rs1funct3rdopcode00000100000001010010000001111011 把addr的地址加载到源寄存器1,编译器会自动分配rs1用哪一个寄存器(这里是x10),执行sbuf操作,将nice_core的数据导出addr。 3.custom_rowsum ".insn r 0x7b, 6, 6, %0, %1, x0" :"=r"(rowsum) :"r"(addr) opcodefunc3func7rdrs1rs20x7b66%0%1x0 custom3读取rs1、rd的数据第六种操作rowsum地址保存addr地址零寄存器 11110111100000110010110101000000机器码: funct7rs2rs1funct3rdopcode00001100000001010110010111111011把addr、rd的地址加载到读源寄存器1,写目标寄存器rd,编译器会自动分配rs1,rd用哪一个寄存器(这里是x10、x11),执行sumrow操作。 附 riscv 寄存器编号:
    发表于 10-31 08:31

    内联汇编的妙用

    它数据相关,于是借助循环变量i,做累加操作,但结果还是不对。 3、内联汇编(结果正确) 一觉醒来,突然想到内联汇编,决定一试:既然编译器不能如我所愿,那可以把想要的部分先自己编译。
    发表于 10-31 06:28

    简单的内联汇编介绍

    ,只能采用内嵌汇编指令(CSR指令)的方式,才能对 CSR 寄存器进行操作。 内联汇编程序 内敛汇编函数格式: asm volatile ( 汇编
    发表于 10-30 08:04

    RISC-V的工具链GCC内联汇编

    GCC内联汇编语法规则如下: asmvolatile( 汇编指令列表 :输出操作数 :输入操作数 :可能影响的寄存器或存储器); 以下分别详细介绍: 1.关键词“asm”是GCC的关键词,表示进行
    发表于 10-30 06:59

    蜂鸟自定义指令软件讲解和内联汇编(一)

    内联汇编介绍 asm volatile(“.insn r 0x7b, 2, 1, x0, %1, x0” : “=r”(zero) : “r”(addr)); (1)GCC内联汇编
    发表于 10-24 10:51

    GCC内联汇编

    GCC内联汇编 在蜂鸟内核的NICE协处理器扩展demo的insn.h文件中存在下面一段指令,用于定义对协处理器调用指令,demo中协处理器支持三条指令:lbuf从内存中load数据至行数据缓存
    发表于 10-24 07:46

    通过内联汇编调用乘法指令mulh\\mulhsu\\mulhu

    mulh、mulhu与mulhsu指令,不能直接验证,需要其他方法。因此,我们想到了在C语言中嵌入内联汇编,分别生成三条指令的接口函数,之后便只需在C语言中调用者三中函数便可执行mulh、mulhu
    发表于 10-24 06:52

    浮点运算单元的实现——浮点指令内联汇编(一)

    代码实现 在硬件实现FPU后,可通过内联汇编的方式,指定浮点指令对其进行测试,以下罗列出相关测试代码。 //fmadds_test __STATIC_FORCEINLINE float
    发表于 10-23 06:51

    浮点运算单元的实现——浮点指令内联汇编(二)

    代码实现 在硬件实现FPU后,可通过内联汇编的方式,指定浮点指令对其进行测试,以下罗列出相关测试代码。如有错误,欢迎大家评论指出。 //feqs_test
    发表于 10-23 06:17

    浮点运算单元的实现——浮点指令内联汇编(三)

    代码实现 在编写完浮点指令相关的内联函数后,参考nice_demo的例子,将其全放在一个头文件中,并编写浮点指令测试代码,代码和结果如下。如有错误,欢迎大家评论指出。 主函数代码 int
    发表于 10-22 08:08

    宏集HMI-4G套装,轻松搞定“数据上云+异地远程运维”

    工业现场设备分散、环境复杂、网络部署?宏集 HMI-4G 套装一站搞定!轻松打破数据孤岛,实现数据上云与远程运维。文末附有真实客户案例,欢迎查阅参考。
    的头像 发表于 08-14 16:46 1275次阅读
    宏集HMI-4G套装,轻松<b class='flag-5'>搞定</b>“数据上云+异地远程运维”

    C语言中的内联函数与宏

    在C编程中,内联函数和宏都用于避免函数调用的开销并编写可复用的逻辑部分,但它们在工作方式和安全性方面存在显著差异。
    的头像 发表于 07-25 15:10 2149次阅读
    C语言中的<b class='flag-5'>内联</b>函数与宏

    MSP430常用内联函数说明

    电子发烧友网站提供《MSP430常用内联函数说明.docx》资料免费下载
    发表于 06-05 17:20 0次下载