gcc 内聯彙編
文章目錄
- gcc 内聯彙編
- 前言
- 一、GCC asm 聲明
-
- 小例子
- 二、優化C代碼
- 三、MORE
-
- 内嵌彙編作為預處理宏
- C 樁函數
- 替換C變量的符号名
- 替換C函數的符号名
- 強制使用特定的寄存器
- 臨時使用寄存器
- 寄存器的用途
- Common pitfalls
- 四、破壞描述部分詳解
-
- 1、編譯器優化介紹
- 2、C語言關鍵字volatile
- 3、Memory
前言
gcc 内聯彙編介紹,轉載 https://blog.csdn.net/lhf_tiger/article/details/32343851
對于基于ARM的RISC處理器,GNU C編譯器提供了在C代碼中内嵌彙編的功能。這種非常酷的特性提供了C代碼沒有的功能,比如手動優化軟體關鍵部分的代碼、使用相關的處理器指令。
這裡設想了讀者是熟練編寫ARM彙程式設計式讀者,因為該片文檔不是ARM彙編手冊。同樣也不是C語言手冊。
這篇文檔假設使用的是GCC 4 的版本,但是對于早期的版本也有效。
一、GCC asm 聲明
小例子
讓我們以一個簡單的例子開始。就像C中的聲明一樣,下面的聲明代碼可能出現在你的代碼中。
/* NOP 例子 */
asm("mov r0,r0");
該語句的作用是将r0移動到r0中。換句話講他并不幹任何事。典型的就是NOP指令,作用就是短時的延時。
請接着閱讀和學習這篇文檔,因為該聲明并不像你想象的和其他的C語句一樣。内嵌彙編使用彙編指令就像在純彙程式設計式中使用的方法一樣。可以在一個asm聲明中寫多個彙編指令。但是為了增加程式的可讀性,最好将每一個彙編指令單獨放一行。
asm(
"mov r0, r0\n\t"
"mov r0, r0\n\t"
"mov r0, r0\n\t"
"mov r0, r0"
);
換行符和制表符的使用可以使得指令清單看起來變得美觀。你第一次看起來可能有點怪異,但是當C編譯器編譯C語句的是候,它就是按照上面(換行和制表)生成彙編的。到目前為止,彙編指令和你寫的純彙程式設計式中的代碼沒什麼差別。但是對比其它的C聲明,asm的常量和寄存器的處理是不一樣的。通用的内嵌彙編模版是這樣的。
彙編和C語句這間的聯系是通過上面asm聲明中可選的output operand list和input operand
list。
Clobber list後面再講。
下面是将C語言的一個整型變量傳遞給彙編,邏輯左移一位後在傳遞給C語言的另外一個整型變量。
/* Rotating bits example */
asm("mov %[result], %[value], ror #1" : [result] "=r" (y) : [value] "r" (x));
每一個asm語句被冒号(:)分成了四個部分。
彙編指令放在第一部分中的" "中間。
接下來是冒号後的可選擇的output operand list,每一個條目是由一對[](方括号)和被他包括的符号名組成,它後面跟着限制性字元串,再後面是圓括号和它括着的C變量。這個例子中隻有一個條目。
接着冒号後面是輸入操作符清單,它的文法和輸入操作清單一樣
破壞符清單,在本例中沒有使用
就像上面的NOP例子,asm聲明的4個部分中,隻要最尾部沒有使用的部分都可以省略。但是有有一點要注意的是,上面的4個部分中隻要後面的還要使用,前面的部分沒有使用也不能省略,必須空但是保留冒号。
下面的一個例子就是設定ARM Soc的CPSR寄存器,它有input但是沒有output operand。
即使彙編代碼沒有使用,代碼部分也要保留白字元串。下面的例子使用了一個特别的破壞符,目的就是告訴編譯器記憶體被修改過了。
這裡的破壞符在下面的優化部分在講解。
為了增加代碼的可讀性,你可以使用換行,空格,還有C風格的注釋。
asm("mov %[result], %[value], ror #1"
: [result]"=r" (y) /* Rotation result. */
: [value]"r" (x) /* Rotated value. */
: /* No clobbers */
);
在代碼部分%後面跟着的是後面兩個部分方括号中的符号,它指的是相同符号操作清單中的一個條目。
%[result]表示第二部分的C變量y,%[value]表示三部分的C變量x;
符号操作符的名字使用了獨立的命名空間。這就意味着它使用的是其他的符号表。簡單一點就是說你不必關心使用的符号名在C代碼中已經使用了。
在早期的C代碼中,循環移位的例子必須要這麼寫:
在彙編代碼中操作數的引用使用的是%後面跟一個數字,%1代表第一個操作數,%2代碼第二個操作數,往後的類推。這個方法目前最新的編譯器還是支援的。但是它不便于維護代碼。試想一下,你寫了大量的彙編指令的代碼,要是你想插入一個操作數,那麼你就不得不從新修改操作數編号。
二、優化C代碼
有兩種情況決定了你必須使用彙編。
- C限制了你更加貼近底層操作硬體,比如,C中沒有直接修改程式狀态寄存器(PSR)的聲明。
- 就是要寫出更加優化的代碼。毫無疑問GNU C代碼優化器做的很好,但是他的結果和我們手工寫的彙編代碼相差很遠。
這一部分有一點很重要,也是被别人忽視最多的就是:我們在C代碼中通過内嵌彙編指令添加的彙編代碼,也是要被C編譯器的優化器處理的。讓我們下面做個試驗來看看吧。
下面是代碼執行個體。
[email protected]:~/embedded/basic-C$ arm-linux-gcc -c test.c
[email protected]:~/embedded/basic-C$ arm-linux-objdump -D test.o
00309DE5 ldr r3, [sp, #0] @ x, x
E330A0E1 mov r3, r3, ror #1 @ tmp, x
04308DE5 str r3, [sp, #4] @ tmp, y
編譯器選擇r3作為循環移位使用。它也完全可以選擇為每一個C變量配置設定寄存器。Load或者store一個值并不顯式的進行。下面是其它編譯器的編譯結果。
E420A0E1 mov r2, r4, ror #1 @ y, x
編譯器為每一個操作數選擇一個相應的寄存器,将操作過的值cache到r4中,然後傳遞該值到r2中。這個過程你能了解不?
有的時候這個過程變得更加糟糕。有時候編譯器甚至完全抛棄你嵌入的彙編代碼。C編譯器的這種行為,取決于代碼優化器的政策和嵌入彙編所處的上下文。如果在内嵌彙編語句中不使用任何輸出部分,那麼C代碼優化器很有可能将該内嵌語句完全删除。比如NOP例子,我們可以使用它作為延時操作,但是對于編譯器認為這影響了程式的執行速速,認為它是沒有任何意義的。
上面的解決方法還是有的。那就是使用volatile關鍵字。它的作用就是禁止優化器優化。将NOP例子修改過後如下:
/* NOP example, revised */
asm volatile("mov r0, r0");
下面還有更多的煩惱等着我們。一個設計精細的優化器可能重新排列代碼。看下面的代碼:
i++;
if (j == 1)
x += 3;
i++;
優化器肯定是要從新組織代碼的,兩個i++并沒有對if的條件産生影響。更進一步的來講,i的值增加2,僅僅使用一條ARM彙編指令。因而代碼要重新組織如下:
if (j == 1)
x += 3;
i += 2;
這樣節省了一條ARM指令。結果是:這些操作并沒有得到許可。</font
這些将對你的代碼産生很到的影響,這将在下面介紹。下面的代碼是c乘b,其中c和b中的一個或者兩個可能會被中斷處理程式修改。進入該代碼前先禁止中斷,執行完該代碼後再開啟中斷。
asm volatile("mrs r12, cpsr\n\t"
"orr r12, r12, #0xC0\n\t"
"msr cpsr_c, r12\n\t" ::: "r12", "cc");
c *= b; /* This may fail. */
asm volatile("mrs r12, cpsr\n"
"bic r12, r12, #0xC0\n"
"msr cpsr_c, r12" ::: "r12", "cc");
但是不幸的是針對上面的代碼,優化器決定先執行乘法然後執行兩個内嵌彙編,或相反。這樣将會使得我們的代碼變得毫無意義。
我們可以使用clobber list幫忙。上面例子中的clobber list如下:
上面的clobber list将會将向編譯器傳達如下資訊,修改了r12和程式狀态寄存器的标志位。Btw,直接指明使用的寄存器,将有可能阻止了最好的優化結果。通常你隻要傳遞一個變量,然後讓編譯器自己選擇适合的寄存器。另外寄存器名,cc(condition registor 狀态寄存器标志位),memory都是在clobber list上有效的關鍵字。它用來向編譯器指明,内嵌彙編指令改變了記憶體中的值。這将強迫編譯器在執行彙編代碼前存儲所有緩存的值,然後在執行完彙編代碼後重新加載該值。這将保留程式的執行順序,因為在使用了帶有memory clobber的asm聲明後,所有變量的内容都是不可預測的。
asm volatile("mrs r12, cpsr\n\t"
"orr r12, r12, #0xC0\n\t"
"msr cpsr_c, r12\n\t" :: : "r12", "cc", "memory");
c *= b; /* This is safe. */
asm volatile("mrs r12, cpsr\n"
"bic r12, r12, #0xC0\n"
"msr cpsr_c, r12" ::: "r12", "cc", "memory");
使所有的緩存的值都無效,隻是局部最優(suboptimal)。你可以有選擇性的添加dummy operand 來人工添加依賴。
asm volatile("mrs r12, cpsr\n\t"
"orr r12, r12, #0xC0\n\t"
"msr cpsr_c, r12\n\t" : "=X" (b) :: "r12", "cc");
c *= b; /* This is safe. */
asm volatile("mrs r12, cpsr\n"
"bic r12, r12, #0xC0\n"
"msr cpsr_c, r12" :: "X" (c) : "r12", "cc");
上面的第一個asm試圖修改變量先b,第二個asm試圖修改c。這将保留三個語句的執行順序,而不要使緩存的變量無效。
了解優化器對内嵌彙編的影響很重要。如果你讀到這裡還是雲裡霧裡,最好是在看下個主題之前再把這段文章讀幾遍_。
#三、 Input and output operands
前面我們學到,每一個input和output operand,由被方括号[]中的符号名,限制字元串,圓括号中的C表達式構成。
這些限制性字元串有哪些,為什麼我們需要他們?你應該知道每一條彙編指令隻接受特定類型的操作數。
例如:跳轉指令期望的跳轉目标位址。不是所有的記憶體位址都是有效的。因為最後的opcode隻接受24位偏移。但沖突的是跳轉指令和資料交換指令都希望寄存器中存儲的是32位的目标位址。在所有的例子中,C傳給operand的可能是函數指針。是以面對傳給内嵌彙編的常量、指針、變量,編譯器必須要知道怎樣組織到彙編代碼中。
對于ARM核的處理器,GCC 4 提供了以下的限制。
Constraint | Usage in ARM state | Usage in Thumb state |
---|---|---|
f | Floating point registers f0 … f7 | Not available |
h | Not available | Registers r8…r15 |
G | Immediate floating point constant | Not available |
H | Same a G, but negated | Not available |
I | Immediate value in data processing instructions e.g. ORR R0, R0, #operand | Constant in the range 0 … 255 e.g. SWI operand |
J | Indexing constants -4095 … 4095 e.g. LDR R1, [PC, #operand] | Constant in the range -255 … -1 e.g. SUB R0, R0, #operand |
K | Same as I, but inverted | Same as I, but shifted |
L | Same as I, but negated | Constant in the range -7 … 7 e.g. SUB R0, R1, #operand |
l | Same as r | Registers r0…r7 e.g. PUSH operand |
M | Constant in the range of 0 … 32 or a power of 2 e.g. MOV R2, R1, ROR #operand | Constant that is a multiple of 4 in the range of 0 … 1020 e.g. ADD R0, SP, #operand |
m | Any valid memory address | |
N | Not available | Constant in the range of 0 … 31 e.g. LSL R0, R1, #operand |
O | Not available | Constant that is a multiple of 4 in the range of -508 … 508e.g. ADD SP, #operand |
r | General register r0 … r15 e.g. SUB operand1, operand2, operand3 | Not available |
w | Vector floating point registers s0 … s31 | Not available |
X | Any operand |
限制字元可能要單個modifier訓示。要是沒有modifier訓示的預設為read-only operand。
Modifier | Specifies |
---|---|
= | Write-only operand, usually used for all output operands |
+ | Read-write operand, must be listed as an output operand |
& | A register that should be used for output only |
Output operands必須為write-only,相應C表達式的值必須是左值。Input operands必須為read-only。C編譯器是沒有能力做這個檢查。
比較嚴格的規則是:不要試圖向input operand寫。但是如果你想要使用相同的operand作為input和output。限制性modifier(+)可以達到效果。例子如下:
和上面例子不一樣的是,最後的結果存儲在input variable中。
可能modifier + 不支援早期的編譯器版本。慶幸的是這裡提供了其他解決辦法,該方法在最新的編譯器中依然有效。對于input operators有可能使用單一的數字n在限制字元串中。使用數字n可以告訴編譯器使用的第n個operand,operand都是以0開始計數。下面是例子:
限制性字元串“0”告訴編譯器,使用和第一個output operand使用同樣input register。
請注意,在相反的情況下不會自動實作。如果我沒告訴編譯器那樣做,編譯器也有可能為input和output選擇相同的寄存器。第一個例子中就為input和output選擇了r3。
在多數情況下這沒有什麼,但是如果在input使用前output已經被修改過了,這将是緻命的。在input和output使用不同寄存器的情況下,你必須使用&modifier來限制output operand。下面是代碼示例:
asm volatile("ldr %0, [%1]" "\n\t"
"str %2, [%1, #4]" "\n\t"
: "=&r" (rdv)
: "r" (&table), "r" (wdv)
: "memory");
在以張表中讀取一個值然後在寫到該表的另一個位置。
三、MORE
内嵌彙編作為預處理宏
要是經常使用部分彙編,最好的方法是将它以宏的形式定義在頭檔案中。使用該頭檔案在嚴格的ANSI模式下會出現警告。為了避免該類問題,可以使用__asm__代替asm,__volatile__代替volatile。這可以等同于别名。下面就是個例程:
#define BYTESWAP(val) \
__asm__ __volatile__ ( \
"eor r3, %1, %1, ror #16\n\t" \
"bic r3, r3, #0x00FF0000\n\t" \
"mov %0, %1, ror #8\n\t" \
"eor %0, %0, r3, lsr #8" \
: "=r" (val) \
: "0"(val) \
: "r3", "cc" \
);
C 樁函數
宏定義包含的是相同的代碼。這在大型routine中是不可以接受的。這種情況下最好定義個樁函數。
unsigned long ByteSwap(unsigned long val)
{
asm volatile (
"eor r3, %1, %1, ror #16\n\t"
"bic r3, r3, #0x00FF0000\n\t"
"mov %0, %1, ror #8\n\t"
"eor %0, %0, r3, lsr #8"
: "=r" (val)
: "0"(val)
: "r3"
);
return val;
}
替換C變量的符号名
預設的情況下,GCC使用同函數或者變量相同的符号名。你可以使用asm聲明,為彙編代碼指定一個不同的符号名
這個聲明告訴編譯器使用了符号名clock代替了具體的值。
替換C函數的符号名
為了改變函數名,你需要一個原型聲明,因為編譯器不接受在函數定義中出現asm關鍵字。
調用函數calc()将會建立調用函數CALCULATE的彙編指令。
強制使用特定的寄存器
局部變量可能存儲在一個寄存器中。你可以利用内嵌彙編為該變量指定一個特定的寄存器。
void Count(void) {
register unsigned char counter asm("r3");
... some code...
asm volatile("eor r3, r3, r3");
... more code...
}
彙編指令“eor r3, r3, r3”,會将r3清零。
Waring:該例子在到多數情況下是有問題的,因為這将和優化器相沖突。因為GCC不會預留其它寄存器。要是優化器認為該變量在以後一段時間沒有使用,那麼該寄存器将會被再次使用。但是編譯器并沒有能力去檢查是否和編譯器預先定義的寄存器有沖突。如果你用這種方式指定了太多的寄存器,編譯器将會在代碼生成的時候耗盡寄存器的。
臨時使用寄存器
如果你使用了寄存器,而你沒有在input或output operand傳遞,那麼你就必須向編譯器指明這些。下面的例子中使用r3作為scratch 寄存器,通過在clobber list中寫r3,來讓編譯器得知使用該寄存器。由于ands指令跟新了狀态寄存器的标志位,使用cc在clobber list中指明。
asm volatile(
"ands r3, %1, #3" "\n\t"
"eor %0, %0, r3" "\n\t"
"addne %0, #4"
: "=r" (len)
: "0" (len)
: "cc", "r3"
);
最好的方法是使用樁函數并且使用局部臨時變量。
with n is in the mentioned range of 0 to 255 and x is an even number in the range of 0 to 24. Because of rotation, x may be set to 26, 28 or 30, in which case bits 37 to 32 are folded to bits 5 to 0 resp. Last not least, the binary complement of these values may be given, when using mvn instead of mov.
Sometimes you need to jump to a fixed memory address, which may be defined by a preprocessor macro. You can use the following assembly code:
ldr r3, =JMPADDR
bx r3
This will work with any legal address value. If the constant fits (for example 0x20000000), then the smart assembler will convert this to
mov r3, #0x20000000
bx r3
If it doesn’t fit (for example 0x00F000F0), then the assembler will load the value from the literal pool.
ldr r3, .L1
bx r3
...
.L1: .word 0x00F000F0
With inline assembly it works in the same way. But instead of using ldr, you can simply provide a constant as a register value:
Depending on the actual value of the constant, either mov, ldr or any of its variants is used. If JMPADDR is defined as 0xFFFFFF00, then the resulting code will be similar to
mvn r3, #0xFF
bx r3
The real world is more complicated. It may happen, that we need to load a specific register with a constant. Let’s assume, that we want to call a subroutine, but we want to return to another address than the one that follows our branch. This is can be useful when embedded firmware returns from main. In this case we need to load the link register. Here is the assembly code:
ldr lr, =JMPADDR
ldr r3, main
bx r3
Any idea how to implement this in inline assembly? Here is a solution:
asm volatile(
"mov lr, %1\n\t"
"bx %0\n\t"
: : "r" (main), "I" (JMPADDR));
But there is still a problem. We use mov here and this will work as long as the value of JMPADDR fits. The resulting code will be the same than what we get in pure assembly code. If it doesn’t fit, then we need ldr instead. But unfortunately there is no way to express
ldr lr, =JMPADDR
in inline assembly. Instead, we must write
asm volatile(
"mov lr, %1\n\t"
"bx %0\n\t"
: : "r" (main), "r" (JMPADDR));
Compared to the pure assembly code, we end up with an additional statement, using an additional register.
ldr r3, .L1
ldr r2, .L2
mov lr, r2
bx r3
寄存器的用途
比較好的方法是分析編譯後的彙編清單,并且學習C 編譯器生成的代碼。下面的清單是編譯器将ARM核寄存器的典型用途,知道這些将有助于了解代碼。
Register | Alt. Name | Usage |
---|---|---|
r0 | a1 | First function argument Integer function result Scratch register |
r1 | a2 | Second function argument Scratch register |
r2 | a3 | Third function argument Scratch register |
r3 | a4 | Fourth function argument Scratch register |
r4 | v1 | Register variable |
r5 | v2 | Register variable |
r6 | v3 | Register variable |
r7 | v4 | Register variable |
r8 | v5 | Register variable |
r9 | v6 rfp | Register variable Real frame pointer |
r10 | sl | Stack limit |
r11 | fp | Argument pointer |
r12 | ip | Temporary workspace |
r13 | sp | Stack pointer |
r14 | lr | Link register Workspace |
r15 | pc | Program counter |
Common pitfalls
Instruction sequence
Developers often expect, that a sequence of instructions remains in the final code as specified in the source code. This assumption is wrong and often introduces hard to find bugs. Actually, asm statements are processed by the optimizer in the same way as other C statements. They may be rearranged if dependencies allow this.
The chapter “C code optimization” discusses the details and offers solutions.
Defining a variable as a specific register
Even if a variable had been forcibly assigned to a specific register, the resulting code may not work as expected. Consider the following snippet:
int foo(int n1, int n2) {
register int n3 asm("r7") = n2;
asm("mov r7, #4");
return n3;
}
The compiler is instructed to use r7 as a local variable n3, which is initialized by parameter n2. Then the inlined assembly statement sets r7 to 4, which should be finally returned. However, this may go completely wrong. Remember, that compiler cannot recognize, what’s happening inside the inline assembly. But the optimizer is smart on the C code, generating the following assembly code.
foo:
mov r7, #4
mov r0, r1
bx lr
Instead of returning r7, the value of n2 is returned, which had been passed to our function in r1. What happed here? Well, while the final code still contains our inline assembly statement, the C code optimizer decided, that n3 is not required. It directly returns parameter n2 instead.
Just assigning a variable to a fixed register does not mean, that the C compiler will use that variable. We still have to tell the compiler, that a variable is modified inside the inline assembly operation.
For the given example, we need to extend the asm statement with an output operator:
asm("mov %0, #4" : "=l" (n3));
Now the C compiler is aware, that n3 is modified and will generate the expected result:
foo:
push {r7, lr}
mov r7, #4
mov r0, r7
pop {r7, pc}
Executing in Thumb status
Be aware, that, depending on the given compile options, the compiler may switch to thumb state. Using inline assembler with instructions that are not available in thumb state will result in cryptic compile errors.
Assembly code size
In most cases the compiler will correctly determine the size of the assembler instruction, but it may become confused by assembler macros. Better avoid them.
In case you are confused: This is about assembly language macros, not C preprocessor macros. It is fine to use the latter.
Labels
Within the assembler instruction you can use labels as jump targets. However, you must not jump from one assembler instruction into another. The optimizer knows nothing about those branches and may generate bad code.
Preprocessor macros
Inline assembly instruction cannot contain preprocessor macros, because for the preprocessor these instruction are nothing else but string constants.
If your assembly code must refer to values that are defined by macros, see the chapter about “Using constants” above.
四、破壞描述部分詳解
破壞描述符用于通知編譯器我們使用了哪些寄存器或記憶體,由逗号格開的字元串組成,每個字元串描述一種情況,一般是寄存器名;除寄存器外還有"memory"。例如:“%eax”,“%ebx”,"memory"等。
"memory"比較特殊,可能是内嵌彙編中最難懂部分。為解釋清楚它,先介紹一下編譯器的優化知識,再看C關鍵字volatile。最後去看該描述符。
1、編譯器優化介紹
記憶體通路速度遠不及CPU處理速度,為提高機器整體性能,在硬體上引入硬體高速緩存Cache,加速對記憶體的通路。另外在現代CPU中指令的執行并不一定嚴格按照順序執行,沒有相關性的指令可以亂序執行,以充分利用CPU的指令流水線,提高執行速度。以上是硬體級别的優化。再看軟體一級的優化:一種是在編寫代碼時由程式員優化,另一種是由編譯器進行優化。編譯器優化常用的方法有:将記憶體變量緩存到寄存器;調整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令。對正常記憶體進行優化的時候,這些優化是透明的,而且效率很好。由編譯器優化或者硬體重新排序引起的問題的解決辦法是在從硬體(或者其他處理器)的角度看必須以特定順序執行的操作之間設定記憶體屏障(memory barrier),linux 提供了一個宏解決編譯器的執行順序問題。
void barrier(void)
這個函數通知編譯器插入一個記憶體屏障,但對硬體無效,編譯後的代碼會把目前CPU寄存器中的所有修改過的數值存入記憶體,需要這些資料的時候再重新從記憶體中讀出。
2、C語言關鍵字volatile
C 語言關鍵字volatile(注意它是用來修飾變量而不是上面介紹的__volatile__)表明某個變量的值可能在外部被改變,是以對這些變量的存取不能緩存到寄存器,每次使用時需要重新存取。該關鍵字在多線程環境下經常使用,因為在編寫多線程的程式時,同一個變量可能被多個線程修改,而程式通過該變量同步各個線程,例如:
DWORD __stdcall threadFunc(LPVOID signal)
{
int* intSignal=reinterpret_cast(signal);
*intSignal=2;
while(*intSignal!=1)
sleep(1000);
return 0;
}
該線程啟動時将intSignal置為2,然後循環等待直到intSignal為1 時退出。顯然intSignal的值必須在外部被改變,否則該線程不會退出。但是實際運作的時候該線程卻不會退出,即使在外部将它的值改為1,看一下對應的僞彙編代碼就明白了:
mov ax,signal
label:
if(ax!=1)
goto label
對于C編譯器來說,它并不知道這個值會被其他線程修改。自然就把它cache在寄存器裡面。記住,C 編譯器是沒有線程概念的!這時候就需要用到volatile。volatile 的本意是指:這個值可能會在目前線程外部被改變。也就是說,我們要在threadFunc中的intSignal前面加上volatile關鍵字,這時候,編譯器知道該變量的值會在外部改變,是以每次通路該變量時會重新讀取,所作的循環變為如下面僞碼所示:
label:
mov ax,signal
if(ax!=1)
goto label
3、Memory
有了上面的知識就不難了解Memory修改描述符了,Memory描述符告知GCC:
1)不要将該段内嵌彙編指令與前面的指令重新排序;也就是在執行内嵌彙編代碼之前,它前面的指令都執行完畢
2)不要将變量緩存到寄存器,因為這段代碼可能會用到記憶體變量,而這些記憶體變量會以不可預知的方式發生改變,是以GCC插入必要的代碼先将緩存到寄存器的變量值寫回記憶體,如果後面又通路這些變量,需要重新通路記憶體。
如果彙編指令修改了記憶體,但是GCC 本身卻察覺不到,因為在輸出部分沒有描述,此時就需要在修改描述部分增加"memory",告訴GCC 記憶體已經被修改,GCC 得知這個資訊後,就會在這段指令之前,插入必要的指令将前面因為優化Cache 到寄存器中的變量值先寫回記憶體,如果以後又要使用這些變量再重新讀取。
使用"volatile"也可以達到這個目的,但是我們在每個變量前增加該關鍵字,不如使用"memory"友善