我试图将程序集插入到C代码中。但是,当我尝试将两个寄存器相乘时,我得到一个错误,要求操作数不匹配。我尝试"mul %%bl, %%cl\n"
(双倍%%,因为它在C代码中)。根据我过去与asm的经验,我认为这应该有效。我也试过"mul %%cl\n"
(首先将bl移动到al),但在这种情况下我从链接器中得到了大量的错误
zad3:(.rodata+0x4): multiple definition of `len'
/tmp/ccJxYyIp.o:(.rodata+0x0): first defined here
zad3: In function `_fini':
(.fini+0x0): multiple definition of `_fini'
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o:(.fini+0x0): first defined here
zad3: In function `data_start':
(.data+0x0): multiple definition of `__data_start'
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o:(.data+0x0): first defined here
zad3: In function `data_start':
(.data+0x8): multiple definition of `__dso_handle'
/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o:(.data+0x0): first defined here
zad3:(.rodata+0x0): multiple definition of `_IO_stdin_used'
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o:(.rodata.cst4+0x0): first defined here
zad3: In function `_start':
(.text+0x0): multiple definition of `_start'
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o: (.text+0x0): first defined here
zad3: In function `data_start':
(.data+0x10): multiple definition of `str'
/tmp/ccJxYyIp.o:(.data+0x0): first defined here
/usr/bin/ld: Warning: size of symbol `str' changed from 4 in /tmp/ccJxYyIp.o to 9 in zad3
zad3: In function `main':
(.text+0xf6): multiple definition of `main'
/tmp/ccJxYyIp.o:zad3.c:(.text+0x0): first defined here
zad3: In function `_init':
(.init+0x0): multiple definition of `_init'
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o:(.init+0x0): first defined here
/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o:(.tm_clone_table+0x0): multiple definition of `__TMC_END__'
zad3:(.data+0x20): first defined here
/usr/bin/ld: error in zad3(.eh_frame); no .eh_frame_hdr table will be created.
collect2: error: ld returned 1 exit status
根据我的理解,它告诉我我几次定义了len和其他一些变量,但我看不出这个多重定义。
我的程序的目标是获取一串数字并计算它们的总和,但使用2作为基础。所以,让我们说字符串是293,然后我想算2*2^2+9*2^1+3*2^0
代码:
#include <stdio.h>
char str[] = "543";
const int len = 3;
int main(void)
{
asm(
"mov $0, %%rbx \n"
"mov $1, %%rcx \n"
"potega: \n"
"shl $1, %%cl \n"
"inc %%rbx \n"
"cmp len, %%ebx \n"
"jl potega \n"
"mov $0, %%rbx \n"
"petla: \n"
"mov (%0, %%rbx, 1), %%al \n"
"sub $48, %%al \n"
"mul %%al, %%cl \n"
"shr $1, %%cl \n"
"add $48, %%al \n"
"mov %%al, (%0, %%rbx, 1) \n"
"inc %%rbx \n"
"cmp len, %%ebx \n"
"jl petla \n"
:"r"(&str)
:"%rax", "%rbx", "%rcx"
);
printf("Wynik: %s\n", str);
return 0;
}
答案 0 :(得分:1)
虽然我尽量避免做人的作业&#34;对于他们来说,你已经解决了这个问题,并且考虑到它已经过了一个多星期,可能已经把它转了。
因此,在查看最终解决方案时,您可能需要考虑做一些不同的事情。没有特别的顺序:
评论。虽然所有代码都需要注释,但asm确实需要注释。正如您从我的解决方案(见下文)中看到的那样,在代码旁边发表评论确实有助于澄清代码的作用。看起来像家庭作业项目几乎不需要它们。但是自从你在这里发布了这个,89人试图阅读这段代码。评论会让我们所有人都更容易。更不用说它会让你的未来自我生活更轻松,&#39;当你几个月后回来试图维持它。评论。努夫说。
归零寄存器。虽然mov $0, %%rbx
确实会在rbx中置零,但这不是将寄存器归零的最有效方法。使用xor %%rbx, %%rbx
(显微镜)更快,并产生(稍微)更小的可执行代码。
potega。没有评论,我花了一些时间来理清你在第一个循环中做了什么。您正在使用rbx来跟踪已处理的字符数,并且cl会为每个字符向左移动一个字符。这里有一些想法:
3A。我要做的第一件事就是将shl $1, %%cl
移出循环。不要同时进行增量和移位,只需计算字符数,然后进行适当大小的单个移位。如果你想要换一个可变数量,那么这个数量必须在cl中指定(即shl %%cl, %%rbx
),这有点复杂。为什么cl?谁知道?这就是shl
的工作原理。因此,您希望在cl而不是rbx中进行计数。
3B。关于这个循环的第二件事与len1
有关。既然你已经知道的大小(它在len1
中),为什么你甚至需要一个循环呢?也许更明智的方法是:
3c上。 C中的字符串以空字符(也称为0)终止。如果你想找到一个字符串的长度,通常你会走字符串,直到你找到它。这样就不需要len1
。
3D。您的代码假定输入字符串有效。如果你通过了将会发生什么&#34; abc&#34;?或者&#34;&#34;?验证参数很无聊,耗时,并使程序更大,运行速度更慢。另一方面,当意外出错时,它会带来巨大的红利。至少你应该指明你对输入的假设。
3e中。使用全局变量通常是个坏主意。您遇到命名冲突(两个文件都使用名称len1),几个不同文件中的代码都改变了值(使得难以追踪错误)并且它可以使您的程序比它需要的更大。有时全局变量很有用,但这似乎不是其中之一。这里唯一的目的似乎是允许从asm中访问这些变量,还有其他方法可以做到这一点。
3f中。您使用%0
来引用str
。这工作(并且比直接访问全局符号更好),但它比它需要的更难阅读。您可以将名称与参数相关联,然后使用该名称。
让我们暂时休息一下,看看我们到目前为止所做的事情:
"xor %%rcx, %%rcx\n" // Zero the strlen count
// Count how many characters in string
"potega%=: \n\t"
"mov (%[pstr], %%rcx), %%bl\n\t" // Read the next char
"test %%bl, %%bl \n\t" // Check for 0 at end of string
"jz kont%= \n\t"
"cmp $'0', %%bl\n\t" // Ensure digit is 0-9
"jl gotowe%=\n\t"
"cmp $'9', %%bl\n\t"
"jg gotowe%=\n\t"
"inc %%rcx \n\t" // Increment index/len
"jmp potega%= \n"
"kont%=:\n\t"
// rcx = the number of character in the string excluding null
您会注意到我在所有标签的末尾都使用%=
。您可以在gcc docs中了解它的作用,但大多只是在标签上附加一个数字。为什么这样?好吧,如果你想尝试在一次运行中计算多个字符串(就像我在下面做的那样),你可能会多次调用这个代码。但是编译器(他们是那些棘手的恶魔)可能会选择&#34; inline&#34;你的汇编程序。这意味着您将拥有几个代码块,这些代码在同一个例程中都具有相同的标签名称。哪会导致你的编译失败。
请注意,我不会检查字符串是否过长&#34;或NULL。留给学生练习......
好的,还有什么?
petla。我的代码大多与你的代码相匹配。
4A。我确实改为sub $'0', %%al
而不是仅仅使用48美元。它做同样的事情,但减去&#39; 0&#39;在我看来,更多的是自我记录。&#34;
4b中。我还略微重新安排了将shr
放在最后的内容。为什么这样?您可以使用cmp
和jz
来查看退出循环的时间。 cmp
的工作方式是在标志寄存器中设置一些标志,然后jz
查看这些标志以确定是否跳转。但是shr
也会设置这些标志。每次换班时,你都会移动那个&#39; 1&#39;进一步向右。如果它位于最右边的位置并且再将它移动一次会发生什么?你得到零。此时&#34;如果不为零则跳跃&#34; (又名jnz
)按预期工作。既然你必须做shr
,为什么不用它来告诉你什么时候退出循环?
这让我:
"petla%=:\n\t"
"mov (%[pstr], %%rcx, 1), %%al\n\t" // read the next char
"sub $'0', %%al\n\t" // convert char to value
"mul %%bl\n\t" // mul bl * al -> ax
"add %%ax, %[res]\n\t" // Accumulate result
"inc %%rcx\n\t" // move to next char
"shr $1, %%rbx\n\t" // decrease our exponent
"jnz petla%=\n" // Has our exponent gone to 0?
"gotowe%=:"
最后,参数:
:[res] "=r"(result)
:[pstr] "r"(str), "0"(0)
:"%rax", "%rbx", "%rcx", "cc"
我要将结果存储在名为result
的C变量中。由于我使用此约束指定=r
,我知道它存储在寄存器中,但我不知道编译器将选择哪个寄存器。但我不需要。我可以使用%[res]
来引用它并让编译器对其进行排序。同样,我使用%[pstr]
来引用字符串。我可以像你一样使用%0,但是因为我已经添加了result
,pstr
不再是0%0,所以%{{1}现在是%0)。这是使用名称而不是数字的另一个原因。
最后一点(result
)可能需要一些解释。使用"0"(0)
作为约束(而不是说"0"
)告诉编译器将此值放在与参数#0相同的位置。 "r"
表示在启动asm之前存储零。换句话说,将要保留(0)
的寄存器初始化为0.是的,我可以在asm中执行此操作。但我更愿意让编译器为我做这件事。虽然在这样的小程序中可能无关紧要,但让C编译器尽可能多地工作往往会产生最有效的代码。
所以,当我们将这些全部包装在一起时,我得到:
result
注意:没有全局变量。没有len1。只是指向字符串的指针。
实验可能会很有趣,看看你能支持的字符串有多长。使用/*
my_file.c - The goal of this program is to take a string of numbers and
count sum of them but using 2 as a base.
example: "543" -> 5*(2^2)+4*(2^1)+3*(2^0)=31
*/
#include <stdio.h>
void TestOne(const char *str)
{
short result;
// Code assumes str is not NULL. Strings with non-digits and zero
// length strings return 0.
asm(
"xor %%rcx, %%rcx\n" // Zero the strlen count
// Count how many characters in string
"potega%=: \n\t"
"mov (%[pstr], %%rcx), %%bl\n\t" // Read the next char
"test %%bl, %%bl \n\t" // Check for 0 at end of string
"jz kont%= \n\t"
"cmp $'0', %%bl\n\t" // Ensure digit is 0-9
"jl gotowe%=\n\t"
"cmp $'9', %%bl\n\t"
"jg gotowe%=\n\t"
"inc %%rcx \n\t" // Increment index/len
"jmp potega%= \n"
"kont%=:\n\t"
// rcx = the number of character in the string excluding null
"dec %%rcx \n\t" // We want to shift rbx 1 less than pstr length
"jl gotowe%=\n\t" // Check for zero length string
"mov $1, %%rbx\n\t" // Set exponent for first digit
"shl %%cl, %%rbx\n\t"
"xor %%rcx, %%rcx\n" // Reset string index
"petla%=:\n\t"
"mov (%[pstr], %%rcx, 1), %%al\n\t" // read the next char
"sub $'0', %%al\n\t" // convert char to value
"mul %%bl\n\t" // mul bl * al -> ax
"add %%ax, %[res]\n\t" // Accumulate result
"inc %%rcx\n\t" // move to next char
"shr $1, %%rbx\n\t" // decrease our exponent
"jnz petla%=\n" // Has our exponent gone to 0?
"gotowe%=:"
:[res] "=r"(result)
:[pstr] "r"(str), "0"(0)
:"%rax", "%rbx", "%rcx", "cc"
);
printf("Wynik: \"%s\" = %d\n", str, result);
}
int main(){
TestOne("x");
TestOne("");
TestOne("5");
TestOne("54");
TestOne("543");
TestOne("5432");
return 0;
}
,mul %%bl
和add %%ax
适用于像这样的小字符串,但最终会因字符串变长而变得不够(需要short result
或eax
等) 。我也会把它留给你。警告:移动&#39; up&#39;从rax
到mul %%bl
。
关于让编译器做尽可能多的工作的最后一点往往会产生最有效的代码:有时人们会认为,因为他们正在编写汇编程序,这将导致代码比他们编写代码更快用C语言编写。然而,这些人没有考虑到C编译器的全部目的是将C代码转换为汇编程序这一事实。当你打开优化(mul %%bx
)时,编译器几乎肯定会把你(编写良好的)C代码变成比你手工编写的更好的汇编代码。
有成千上万的调整和技巧,就像我在这里提到的一样。编写编译器的人都知道这些。虽然有几个地方的内联asm是有意义的,但聪明的程序员会把这项工作交给那些尽可能编写编译器的疯子。另请参阅this。
我意识到这只是一个学校项目,而你只是按照老师的要求做的,但既然她选择用最困难的方式来教你,也许她没有提到你正在做的事情是你应该(几乎)从未在现实生活中做过的事情。
这篇文章的结果比我预期的要长。希望这里有您可以使用的信息。并原谅我对波兰品牌的尝试。希望我没有说过什么淫秽......
答案 1 :(得分:0)
有人指出 - 是的,这是学生的运动。
当谈到我原来的问题时,当我删除行add $48,%%al \n"
时,它有效。我也切换到了mul %%cl
。
当谈到其他问题时,你指出,我和我的教授谈过,她稍微改变了主意(或者我第一次弄错了 - 不管你发现什么更多可能),现在她想要我回来来自内联函数的一个参数,并说intiger类型很好。这导致我编写了这样的代码(实际上就是我想做的)
例子:&#34; 543&#34; - &GT; 5 *(2 ^ 2)+ 4 *(2 ^ 1)+ 3 *(2 ^ 0)= 31
#include <stdio.h>
char str[] = "543";
const int len = 3;
int len1 = 2;
int result;
int main(){
asm(
"mov $0, %%rbx\n"
"mov $1, %%rcx\n"
"mov $0, %%rdx\n"
"potega: \n"
"inc %%rbx\n"
"shl $1, %%cl\n"
"cmp len1, %%ebx \n"
"jl potega\n"
"mov $0, %%rbx\n"
"petla:\n"
"mov (%0, %%rbx, 1), %%al\n"
"sub $48, %%al\n"
"mul %%cl\n"
"shr $1, %%cl\n"
"add %%al, %%dl\n"
"inc %%rbx\n"
"cmp len, %%ebx\n"
"jl petla\n"
"movl %%edx, result\n"
://"=r"(result)
:"r"(&str), "r"(&result)
:"%rax", "%rbx", "%rcx", "%rdx"
);
printf("Wynik: %d\n", result);
return 0;
}
另外 - 我确实知道,通常你会按照它在评论中显示的方式返回变量,但它没有用,所以根据我教授的建议,我用这种方式编写了程序。
感谢大家的帮助!