为什么可以在GNU C基本内联ASM语句中使用局部变量?

发布于 2025-02-04 04:59:54 字数 1017 浏览 3 评论 0 原文

为什么我不能使用 main 中的局部变量用于基本ASM内联使用?仅在扩展的ASM中允许它,但是为什么呢?

(我知道本地变量在返回地址之后在堆栈上(因此,一旦函数返回就不能使用),但这不应该是不使用它们的理由)

和基本ASM的示例:

int a = 10; //global a
int b = 20; //global b
int result;

int main() {
    asm ( "pusha\n\t"
          "movl a, %eax\n\t"
          "movl b, %ebx\n\t"
          "imull %ebx, %eax\n\t"
          "movl %eax, result\n\t"
          "popa");

    printf("the answer is %d\n", result);
    return 0;
}

扩展的示例:extended:

int main (void) {
    int data1 = 10;  //local var - could be used in extended
    int data2 = 20;
    int result;

    asm ( "imull %%edx, %%ecx\n\t"
          "movl %%ecx, %%eax" 
          : "=a"(result)
          : "d"(data1), "c"(data2));

    printf("The result is %d\n",result);
    return 0;
}

编译: GCC -M32 somefile.c

平台: UNAME -A linux 5.0.0-32总生成#34-ubuntu SMP SMP Wed 2 02:06:48 UTC 2019 x86_64 x86_64 x86_64 x86_64 gnu/linux

Why cannot I use local variables from main to be used in basic asm inline? It is only allowed in extended asm, but why so?

(I know local variables are on the stack after return address (and therefore cannot be used once the function return), but that should not be the reason to not use them)

And example of basic asm:

int a = 10; //global a
int b = 20; //global b
int result;

int main() {
    asm ( "pusha\n\t"
          "movl a, %eax\n\t"
          "movl b, %ebx\n\t"
          "imull %ebx, %eax\n\t"
          "movl %eax, result\n\t"
          "popa");

    printf("the answer is %d\n", result);
    return 0;
}

example of extended:

int main (void) {
    int data1 = 10;  //local var - could be used in extended
    int data2 = 20;
    int result;

    asm ( "imull %%edx, %%ecx\n\t"
          "movl %%ecx, %%eax" 
          : "=a"(result)
          : "d"(data1), "c"(data2));

    printf("The result is %d\n",result);
    return 0;
}

Compiled with:
gcc -m32 somefile.c

platform:
uname -a:
Linux 5.0.0-32-generic #34-Ubuntu SMP Wed Oct 2 02:06:48 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

落花浅忆 2025-02-11 04:59:55

您可以在扩展程序集中使用局部变量,但是您需要告诉扩展程序集构造。考虑:

#include <stdio.h>


int main (void)
{
    int data1 = 10;
    int data2 = 20;
    int result;

    __asm__(
        "   movl    %[mydata1], %[myresult]\n"
        "   imull   %[mydata2], %[myresult]\n"
        : [myresult] "=&r" (result)
        : [mydata1] "r" (data1), [mydata2] "r" (data2));

    printf("The result is %d\n",result);

    return 0;
}

在此 [myResult]中,“ =&amp; r”(结果)说要选择一个将用作输出的寄存器( r )( = =) )lvalue 结果的值,并且该寄存器将在汇编中称为>%[myResult] ,并且必须与输入寄存器不同(<<<<<<<代码>&amp; )。 (您可以在两个地方使用相同的文本, result 而不是 myResult ;我只是在插图中不同。)

类似地, [mydata1]“ r”( data1)说将表达式 data1 的值放入寄存器中,并且将在汇编中称为>%%[mydata1]

我修改了汇编中的代码,以便它仅修改输出寄存器。您的原始代码修改%ECX ,但不告诉编译器它正在这样做。您本可以告诉编译器,通过将“ ECX”放置在第三个之后:之后,这是“ clobbered”寄存器的列表所在的位置。但是,由于我的代码允许编译器分配寄存器,因此我不会在杂物寄存器中的特定寄存器上列出。可能有一种方法可以告诉编译器,其中一个输入寄存器将被修改,但不需要输出,但我不知道。 (文档为在这里。任务,一个更好的解决方案是告诉编译器对输入之一使用相同的寄存器:

    __asm__(
        "   imull   %[mydata1], %[myresult]\n"
        : [myresult] "=r" (result)
        : [mydata1] "r" (data1), [mydata2] "0" (data2));

在此中, 0 带有 data2 表示要使它与操作数0相同。操作数是按照其显示的顺序编号,从第一个输出操作数开始,然后继续进入输入操作数。因此,当汇编代码启动时,%[myResult] 将参考一些登记册,即 data2 已放置了一个登记册,编译器将期望新值的新值结果在完成程序集后将在该寄存器中。

这样做时,您必须将约束与在汇编中使用的事物使用。对于 r 约束,编译器提供一些可以在接受通用处理器寄存器的汇编语言中使用的文本。其他包括 m 用于内存参考,以及 i 用于立即操作数。

You can use local variables in extended assembly, but you need to tell the extended assembly construct about them. Consider:

#include <stdio.h>


int main (void)
{
    int data1 = 10;
    int data2 = 20;
    int result;

    __asm__(
        "   movl    %[mydata1], %[myresult]\n"
        "   imull   %[mydata2], %[myresult]\n"
        : [myresult] "=&r" (result)
        : [mydata1] "r" (data1), [mydata2] "r" (data2));

    printf("The result is %d\n",result);

    return 0;
}

In this [myresult] "=&r" (result) says to select a register (r) that will be used as an output (=) value for the lvalue result, and that register will be referred to in the assembly as %[myresult] and must be different from the input registers (&). (You can use the same text in both places, result instead of myresult; I just made it different for illustration.)

Similarly [mydata1] "r" (data1) says to put the value of expression data1 into a register, and it will be referred to in the assembly as %[mydata1].

I modified the code in the assembly so that it only modifies the output register. Your original code modifies %ecx but does not tell the compiler it is doing that. You could have told the compiler that by putting "ecx" after a third :, which is where the list of “clobbered” registers goes. However, since my code lets the compiler assign a register, I would not have a specific register to list in the clobbered register. There may be a way to tell the compiler that one of the input registers will be modified but is not needed for output, but I do not know. (Documentation is here.) For this task, a better solution is to tell the compiler to use the same register for one of the inputs as the output:

    __asm__(
        "   imull   %[mydata1], %[myresult]\n"
        : [myresult] "=r" (result)
        : [mydata1] "r" (data1), [mydata2] "0" (data2));

In this, the 0 with data2 says to make it the same as operand 0. The operands are numbered in the order they appear, starting with 0 for the first output operand and continuing into the input operands. So, when the assembly code starts, %[myresult] will refer to some register that the value of data2 has been placed in, and the compiler will expect the new value of result to be in that register when the assembly is done.

When doing this, you have to match the constraint with how a thing will be used in assembly. For the r constraint, the compiler supplies some text that can be used in assembly language where a general processor register is accepted. Others include m for a memory reference, and i for an immediate operand.

帝王念 2025-02-11 04:59:55

“基本ASM”和“扩展ASM”之间几乎没有区别。 “基本ASM”只是 __ ASM __ 语句没有输出,输入或clobbers的特殊情况。编译器不会在基本ASM的汇编字符串中替换。如果您需要输入或输出,则必须指定它们,然后人们称之为“扩展ASM”。

实际上,可能可以从“基本ASM”访问外部(甚至是文件范围静态)对象。这是因为这些对象(分别可以)在汇编级别具有符号名称。但是,要执行此类访问权限,您需要谨慎对其是否与位置无关(如果您的代码将链接到库或PIE可执行文件中),并符合其他在链接时间时可能施加的ABI约束,并且有各种考虑的考虑因素编译器可能执行的链接时间优化和其他转换的兼容性。简而言之,这是一个坏主意,因为您无法告诉编译器基本的ASM语句修改了内存。没有办法使其安全。

<代码>“内存” clobber(扩展的ASM)可以使从ASM模板的名称访问静态存储变量安全。

基本ASM的用例是仅修改机器状态的事物,例如 asm(“ Cli”)在内核中以禁用中断,而无需读取或编写任何C变量。 (即使那样,您通常也经常使用“内存” clobber来确保编译器在更改机器状态之前已经完成了较早的内存操作。)

local(自动存储,而不是静态存储)从根本上绝不具有符号名称,因为它们不'' t在一个实例中存在;他们在运行时声明的块的实例有一个对象。因此,访问它们的唯一可能方法是通过输入/输出约束。

来自MSVC-Land的用户可能会发现这令人惊讶,因为MSVC的Inline Assembly方案论文通过将其Inline ASM版本中的本地变量参考转换为堆栈中的销售式访问。但是,Inline ASM提供的版本与优化编译器不兼容,并且使用该类型的Inline ASM在功能中几乎不会进行优化。海湾合作委员会和与UNIX一起成长的较大的编译器世界并没有做任何类似的事情。

There is little distinction between "Basic asm" and "Extended asm"; "basic asm" is just a special case where the __asm__ statement has no lists of outputs, inputs, or clobbers. The compiler does not do % substitution in the assembly string for Basic asm. If you want inputs or outputs you have to specify them, and then it's what people call "extended asm".

In practice, it may be possible to access external (or even file-scope static) objects from "basic asm". This is because these objects will (respectively may) have symbol names at the assembly level. However, to perform such access you need to be careful of whether it is position-independent (if your code will be linked into libraries or PIE executables) and meets other ABI constraints that might be imposed at linking time, and there are various considerations for compatibility with link-time optimization and other transformations the compiler may perform. In short, it's a bad idea because you can't tell the compiler that a basic asm statement modified memory. There's no way to make it safe.

A "memory" clobber (Extended asm) can make it safe to access static-storage variables by name from the asm template.

The use-case for basic asm is things that modify the machine state only, like asm("cli") in a kernel to disable interrupts, without reading or writing any C variables. (Even then, you'd often use a "memory" clobber to make sure the compiler had finished earlier memory operations before changing machine state.)

Local (automatic storage, not static ones) variables fundamentally never have symbol names, because they don't exist in a single instance; there's one object per live instance of the block they're declared in, at runtime. As such, the only possible way to access them is via input/output constraints.

Users coming from MSVC-land may find this surprising since MSVC's inline assembly scheme papers over the issue by transforming local variable references in their version of inline asm into stack-pointer-relative accesses, among other things. The version of inline asm it offers however is not compatible with an optimizing compiler, and little to no optimization can happen in functions using that type of inline asm. GCC and the larger compiler world that grew alongside C out of unix does not do anything similar.

铃予 2025-02-11 04:59:55

您不能安全地在基本ASM语句中使用Globals ;它恰好可以使用优化,但它不安全,您正在滥用语法。

几乎没有理由 ever 使用基本ASM。 d通常需要一个“内存” clobber订购它。负载 /存储到全球。实际上,GCC的 ,因为编译器之间的不同之处,而GCC可能会更改为将其视为将所有内容视为无所事事(因为存在错误的假设的现有错误代码)。这将制作一个基本的ASM语句,该语句使用 push / pop 如果编译器还在其周围生成商店和重新加载,则更加效率更低。

基本上,基本ASM的唯一用例是编写 __属性的正文__(((naked)))函数,其中数据输入/输出/与其他代码的互动遵循ABI的调用约定,而不是任何自定义约定的约束 / clobbers描述了代码的真正内联块。


GNU C内联ASM的设计是,您将其注入编译器的正常ASM输出(然后将其馈送到汇编程序中, )。扩展的ASM使字符串成为一个模板,它可以代替操作数。并且这些约束描述了ASM如何拟合到程序逻辑的数据流以及寄存器的数据流中。

您需要使用语法来准确描述它的作用,而不是解析字符串。解析VAR名称的模板只能解决操作数以求解的语言设计问题的一部分,并且会使编译器的代码更加复杂。 (它必须了解更多有关每个指令的信息,才能知道是否允许内存,注册或立即进行。通常,其机器描述文件只需要知道如何从逻辑操作到ASM,而不是另一个方向。)

您的基本ASM块被打破了,因为您在不告诉编译器的情况下修改C变量。这可能会启用优化(也许只有更复杂的周围代码,但要进行工作与实际安全的事情都不相同。这就是为什么仅仅仅测试GNU C内联ASM代码甚至不足以使其成为将来的证明针对新的编译器和周围代码的更改)。没有隐式“内存” clobber。 (基本ASM与扩展ASM相同,除了不做字符串文字上的替换。因此,您不需要 %% 获得文字在ASM输出中,它隐含地挥发了,就像没有输出的扩展ASM

结果仅由于ASM符号名称与C变量名称完全匹配,因此恰好工作。 约束将使其在GNU/Linux(无领先地下)与确实使用领先的 _

使用扩展的ASM 因为您可以修改输入(“ C” (不告诉编译器寄存器也是输出,例如使用相同寄存器的输出操作数)。
这也是效率低下的:如果 mov 是模板的第一个或最后一个指令,则您几乎总是做错了,应该使用更好的约束。

相反,您可以这样做:

    asm ("imull %%edx, %%ecx\n\t"
          : "=c"(result)
          : "d"(data1), "c"(data2));

或更好,使用“+r”(data2)“ r”(data1)操作数以在执行注册分配时提供编译器免费选择可能强迫编译器发射不必要的 mov 指令。 (请参阅 @eric使用命名操作数和“ = r” 和匹配“ 0” 约束;这等同于“+r” 但让您可以为输入和输出使用不同的C名称。)

查看编译器的ASM output ,以查看ASM语句周围的代码 - 如果要确保有效的话。


由于本地VAR在ASM文本中没有符号 /标签(相反,它们生活在寄存器中或与堆栈或框架指针相抵消,即自动存储),因此在ASM中为其使用符号名称无效。

即使对于全局var,您想要编译器能够尽可能多地围绕ASM进行优化,因此您希望为编译器提供使用已在已处已经存在的全局var的副本的选项寄存器,而不是与商店同步内存中的值,以便您的ASM可以重新加载。

让编译器尝试解析您的ASM,并找出哪些C局部VAR名称是输入,并且输出是可能的。 (但会很复杂。)

但是如果您希望它高效,则需要弄清楚 x 在ASM中可以像EAX一样是寄存器,而不是做一些braindead的事情就像始终将 x 存储在ASM语句之前的内存中,然后用 x 8(%RSP)或其他任何东西替换。 如果您想对输入的何处进行ASM语句控制,则需要以某种形式的限制。以每项操作和基础进行操作是完全有意义的,并且意味着内联弹簧处理不做'''必须知道, bts 可以立即或注册源,但不使用此类机器特定的细节。 (请记住; GCC是一种便携式编译器;将大量的每机金信息烘烤到内联弹药板中是不好的。)

(MSVC强制 _asm {} blocks中的所有C vars bemome是内存。并掉落在非空隙函数的末尾“>'asm','__asm'和'__asm__'之间有什么区别? 也不是语法设计的错!

它被记录为在32位模式下使用寄存器ARG的功能,这 _asm {...} msvc-style语法在其中解析ASM,您使用C var名称。它可能将输入和输出迫使内存,但我尚未检查。


还要注意,GCC的内联ASM语法具有约束,围绕着与GCC Internals机器 - 描述文件用来描述编译器的ISA 相同的约束系统。 ( .md 在GCC源中的文件,该文件告诉编译器有关添加输入数字的指令,该数字在“ r” 寄存器中,并具有mnemonic的文本字符串在

GNU C中 asm 的设计模型是它是优化器的黑框;您必须使用约束来充分描述代码(对优化器)的效果。,如果您抓住了寄存器,则必须告诉编译器。如果您有要销毁的输入操作数,则需要使用带有匹配约束的虚拟输出操作数,或“+r” 操作数来更新相应的C变量值。

如果读取或写以寄存器输入为指向的内存,则必须告诉编译器。

如果您使用堆栈,则必须告诉编译器(但是您不能,所以您必须避免踩踏在红色区域:/使用C ++ Inline ASM中的基本指针寄存器/a>)另请参见 inline-eSembly tag wiki

GCC的设计使您可以为您提供一个编译器在寄存器中输入,并使用相同的寄存器进行其他输出。 (如果不正常,请使用早期关闭约束; gcc的语法旨在有效地包装单个指令,该指令在编写任何输出 之前读取所有输入。)

GCC只能推断出。所有这些来自C var名称出现在ASM源中的东西,我认为控制水平是不可能的。 (至少不合理。)到处都可能会带来令人惊讶的效果,更不用说错过的优化了。您只有在需要最大程度地控制事物的情况下使用内联ASM,因此您想要的最后一件事是使用很多复杂的不透明逻辑来弄清楚该怎么做。

(内联ASM的当前设计足够复杂,与普通C相比,使用不多,因此需要非常复杂的编译器支持的设计可能最终会出现很多编译器错误。)


GNU C内联ASM并不是为低性能低效率而设计的。如果需要简单的话,只需写入纯C或使用内在的内容并让编译器完成其工作。

You can't safely use globals in Basic Asm statements either; it happens to work with optimization disabled but it's not safe and you're abusing the syntax.

There's very little reason to ever use Basic Asm. Even for machine-state control like asm("cli") to disable interrupts, you'd often want a "memory" clobber to order it wrt. loads / stores to globals. In fact, GCC's https://gcc.gnu.org/wiki/ConvertBasicAsmToExtended page recommends never using Basic Asm because it differs between compilers, and GCC might change to treating it as clobbering everything instead of nothing (because of existing buggy code that makes wrong assumptions). This would make a Basic Asm statement that uses push/pop even more inefficient if the compiler is also generating stores and reloads around it.

Basically the only use-case for Basic Asm is writing the body of an __attribute__((naked)) function, where data inputs/outputs / interaction with other code follows the ABI's calling convention, instead of whatever custom convention the constraints / clobbers describe for a truly inline block of code.


The design of GNU C inline asm is that it's text that you inject into the compiler's normal asm output (which is then fed to the assembler, as). Extended asm makes the string a template that it can substitute operands into. And the constraints describe how the asm fits into the data-flow of the program logic, as well as registers it clobbers.

Instead of parsing the string, there is syntax that you need to use to describe exactly what it does. Parsing the template for var names would only solve part of the language-design problem that operands need to solve, and would make the compiler's code more complicated. (It would have to know more about every instruction to know whether memory, register, or immediate was allowed, and stuff like that. Normally its machine-description files only need to know how to go from logical operation to asm, not the other direction.)

Your Basic asm block is broken because you modify C variables without telling the compiler about it. This could break with optimization enabled (maybe only with more complex surrounding code, but happening to work is not the same thing as actually safe. This is why merely testing GNU C inline asm code is not even close to sufficient for it to be future proof against new compilers and changes in surrounding code). There is no implicit "memory" clobber. (Basic asm is the same as Extended asm except for not doing % substitution on the string literal. So you don't need %% to get a literal % in the asm output. It's implicitly volatile like Extended asm with no outputs.)

Also note that if you were targeting i386 MacOS, you'd need _result in your asm. result only happens to work because the asm symbol name exactly matches the C variable name. Using Extended asm constraints would make it portable between GNU/Linux (no leading underscore) vs. other platforms that do use a leading _.

Your Extended asm is broken because you modify an input ("c") (without telling the compiler that register is also an output, e.g. an output operand using the same register).
It's also inefficient: if a mov is the first or last instruction of your template, you're almost always doing it wrong and should have used better constraints.

Instead, you can do:

    asm ("imull %%edx, %%ecx\n\t"
          : "=c"(result)
          : "d"(data1), "c"(data2));

Or better, use "+r"(data2) and "r"(data1) operands to give the compiler free choice when doing register allocation instead of potentially forcing the compiler to emit unnecessary mov instructions. (See @Eric's answer using named operands and "=r" and a matching "0" constraint; that's equivalent to "+r" but lets you use different C names for the input and output.)

Look at the asm output of the compiler to see how code-gen happened around your asm statement, if you want to make sure it was efficient.


Since local vars don't have a symbol / label in the asm text (instead they live in registers or at some offset from the stack or frame pointer, i.e. automatic storage), it can't work to use symbol names for them in asm.

Even for global vars, you want the compiler to be able to optimize around your inline asm as much as possible, so you want to give the compiler the option of using a copy of a global var that's already in a register, instead of getting the value in memory in sync with a store just so your asm can reload that.

Having the compiler try to parse your asm and figure out which C local var names are inputs and outputs would have been possible. (But would be a complication.)

But if you want it to be efficient, you need to figure out when x in the asm can be a register like EAX, instead of doing something braindead like always storing x into memory before the asm statement, and then replacing x with 8(%rsp) or whatever. If you want to give the asm statement control over where inputs can be, you need constraints in some form. Doing it on a per-operand basis makes total sense, and means the inline-asm handling doesn't have to know that bts can take an immediate or register source but not memory, for and other machine-specific details like that. (Remember; GCC is a portable compiler; baking a huge amount of per-machine info into the inline-asm parser would be bad.)

(MSVC forces all C vars in _asm{} blocks to be memory. It's impossible to use to efficiently wrap a single instruction because the input has to bounce through memory, even if you wrap it in a function so you can use the officially-supported hack of leaving a value in EAX and falling off the end of a non-void function. What is the difference between 'asm', '__asm' and '__asm__'? And in practice MSVC's implementation was apparently pretty brittle and hard to maintain, so much so that they removed it for x86-64, and it was documented as not supported in function with register args even in 32-bit mode! That's not the fault of the syntax design, though, just the actual implementation.)

Clang does support -fasm-blocks for _asm { ... } MSVC-style syntax where it parses the asm and you use C var names. It probably forces inputs and outputs into memory but I haven't checked.


Also note that GCC's inline asm syntax with constraints is designed around the same system of constraints that GCC-internals machine-description files use to describe the ISA to the compiler. (The .md files in the GCC source that tell the compiler about an instruction to add numbers that takes inputs in "r" registers, and has the text string for the mnemonic. Notice the "r" and "m" in some examples in https://gcc.gnu.org/onlinedocs/gccint/RTL-Template.html).

The design model of asm in GNU C is that it's a black-box for optimizer; you must fully describe the effects of the code (to the optimizer) using constraints. If you clobber a register, you have to tell the compiler. If you have an input operand that you want to destroy, you need to use a dummy output operand with a matching constraint, or a "+r" operand to update the corresponding C variable's value.

If you read or write memory pointed-to by a register input, you have to tell the compiler. How can I indicate that the memory *pointed* to by an inline ASM argument may be used?

If you use the stack, you have to tell the compiler (but you can't, so instead you have to avoid stepping on the red-zone :/ Using base pointer register in C++ inline asm) See also the inline-assembly tag wiki

GCC's design makes it possible for the compiler to give you an input in a register, and use the same register for a different output. (Use an early-clobber constraint if that's not ok; GCC's syntax is designed to efficiently wrap a single instruction that reads all its inputs before writing any of its outputs.)

If GCC could only infer all of these things from C var names appearing in asm source, I don't think that level of control would be possible. (At least not plausible.) And there'd probably be surprising effects all over the place, not to mention missed optimizations. You only ever use inline asm when you want maximum control over things, so the last thing you want is the compiler using a lot of complex opaque logic to figure out what to do.

(Inline asm is complex enough in its current design, and not used much compared to plain C, so a design that requires very complex compiler support would probably end up with a lot of compiler bugs.)


GNU C inline asm isn't designed for low-performance low-effort. If you want easy, just write in pure C or use intrinsics and let the compiler do its job. (And file missed-optimization bug reports if it makes sub-optimal code.)

凶凌 2025-02-11 04:59:55

这是因为ASM是一种定义的语言,对于同一处理器家族的所有编译器来说都是常见的。使用 __ ASM __ 关键字后,您可以可靠地使用任何好手册来为处理器使用任何好手册,然后开始编写有用的代码。

但是它没有C的定义接口,说实话,如果您不与C代码接口汇编器,那为什么在那里?

有用的非常简单的ASM的示例:生成调试中断;设置浮点寄存器模式(异常/准确性);

每个编译器作者都发明了自己的机制来接口与C。在GCC和Clang中,他们允许您使用他们相当混乱的2步系统来引用输入或输出索引,然后将该索引与本地变量相关联。

该机制是对ASM标准的“扩展”。

当然,ASM并不是真正的标准。更改处理器,您的ASM代码是垃圾。当我们一般谈论坚持C/C ++标准而不使用扩展标准时,我们不会谈论ASM,因为您已经违反了那里的每个可移植性规则。

然后,最重要的是,如果您要调用C函数,或者您的ASM声明可调用C的函数,则必须与编译器的调用约定匹配。这些规则是隐式的。他们限制了您撰写ASM的方式,但根据某些标准,它仍然是合法的ASM。

但是,如果您只是编写自己的ASM函数,并从ASM拨打它们,则可能不会受到C/C ++约定的限制:弥补自己的注册参数规则;在您想要的任何寄存器中返回值;制作堆栈框架,或者不做;通过例外保护堆栈框架 - 谁在乎?

请注意,您可能仍然受到平台可重新定位的代码约定的约束(这些不是“ C”惯例,而是通常使用C语法描述),但这仍然是您可以编写“ Portable” ASM函数的一部分的一种方式,然后使用“扩展”嵌入的ASM调用它们。

This is because asm is a defined language which is common for all compilers on the same processor family. After using the __asm__ keyword, you can reliably use any good manual for the processor to then start writing useful code.

But it does not have a defined interface for C, and lets be honest, if you don't interface your assembler with your C code then why is it there?

Examples of useful very simple asm: generate a debug interrupt; set the floating point register mode (exceptions/accuracy);

Each compiler writer has invented their own mechanism to interface to C. For example in one old compiler you had to declare the variables you want to share as named registers in the C code. In GCC and clang they allow you to use their quite messy 2-step system to reference an input or output index, then associate that index with a local variable.

This mechanism is the "extension" to the asm standard.

Of course, the asm is not really a standard. Change processor and your asm code is trash. When we talk in general about sticking to the c/c++ standards and not using extensions, we don't talk about asm, because you are already breaking every portability rule there is.

Then, on top of that, if you are going to call C functions, or your asm declares functions that are callable by C then you will have to match to the calling conventions of your compiler. These rules are implicit. They constrain the way you write your asm, but it will still be legal asm, by some criteria.

But if you were just writing your own asm functions, and calling them from asm, you may not be constrained so much by the c/c++ conventions: make up your own register argument rules; return values in any register you want; make stack frames, or don't; preserve the stack frame through exceptions - who cares?

Note that you might still be constrained by the platform's relocatable code conventions (these are not "C" conventions, but are often described using C syntax), but this is still one way that you can write a chunk of "portable" asm functions, then call them using "extended" embedded asm.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文