x86 64架构汇编语言

12、内联汇编与混合编程

1、课程目标

  1. 掌握C/C++中内联汇编的使用方法
  2. 理解内联汇编的限制和注意事项
  3. 学会C与汇编的混合链接
  4. 掌握跨语言调用技术

2、名词解释

术语 英文 说明
内联汇编 Inline Assembly 嵌入在C/C++代码中的汇编代码
__asm Asm Keyword MSVC内联汇编关键字
混合编程 Mixed Programming C/C++与汇编联合编程
名称修饰 Name Mangling C++编译器对函数名的编码
extern “C” C Linkage 使用C语言链接方式

3、使用工具

工具 用途
MSVC (x86) 支持内联汇编的编译器
MASM 单独的汇编模块
Dumpbin 查看导出符号

4、技术原理

4.1、内联汇编限制

平台 MSVC支持
x86 完全支持__asm
x64 不支持内联汇编
ARM 不支持内联汇编

4.2、混合编程方式

  1. 内联汇编 - 仅x86
  2. 单独.asm文件 - 所有平台
  3. Intrinsic函数 - 所有平台

5、代码实现

5.1、示例1:基本内联汇编

#include <stdio.h>

// 基本内联汇编语法
int Add(int a, int b) {
    int result;
    __asm {
        mov eax, a
        add eax, b
        mov result, eax
    }
    return result;
}

// 访问局部变量和参数
int Calculate(int x, int y, int z) {
    int temp;
    __asm {
        mov eax, x          ; 直接使用参数名
        imul eax, y         ; x * y
        mov temp, eax       ; 保存中间结果
        add eax, z          ; + z
    }
    return temp + z;
}

// 调用C函数
void __cdecl PrintValue(int val);

void TestCall() {
    __asm {
        push 100
        call PrintValue
        add esp, 4
    }
}

int main() {
    printf("Add(10, 20) = %d\n", Add(10, 20));
    printf("Calculate(3, 4, 5) = %d\n", Calculate(3, 4, 5));
    return 0;
}

void __cdecl PrintValue(int val) {
    printf("Value: %d\n", val);
}

5.2、示例2:寄存器操作

#include <stdio.h>
#include <windows.h>

// 获取CPU信息
void GetCPUID(int function, int* eax, int* ebx, int* ecx, int* edx) {
    __asm {
        mov esi, eax        ; 保存指针
        mov edi, ebx
        
        mov eax, function
        cpuid
        
        ; 保存结果
        push eax
        push ebx
        push ecx
        push edx
        
        mov eax, [esp+12]   ; 原始eax
        mov edi, esi        ; eax指针
        mov [edi], eax
        
        ; ...继续处理其他寄存器
        
        add esp, 16
    }
}

// 读取时间戳计数器
unsigned __int64 ReadTSC() {
    unsigned int lo, hi;
    __asm {
        rdtsc               ; 读取时间戳
        mov lo, eax
        mov hi, edx
    }
    return ((unsigned __int64)hi << 32) | lo;
}

// 实现原子操作
long AtomicIncrement(long* value) {
    long result;
    __asm {
        mov ecx, value
        mov eax, 1
        lock xadd [ecx], eax  ; 原子加
        inc eax               ; 返回新值
        mov result, eax
    }
    return result;
}

int main() {
    printf("TSC: %llu\n", ReadTSC());
    
    long val = 10;
    printf("Before: %ld\n", val);
    printf("After increment: %ld\n", AtomicIncrement(&val));
    printf("Final: %ld\n", val);
    
    return 0;
}

5.3、示例3:汇编模块与C链接

// main.c
#include <stdio.h>

// 声明外部汇编函数
extern int __cdecl AsmAdd(int a, int b);
extern int __stdcall AsmMultiply(int a, int b);
extern void __cdecl AsmMemcpy(void* dest, const void* src, size_t n);

int main() {
    printf("AsmAdd: %d\n", AsmAdd(100, 200));
    printf("AsmMultiply: %d\n", AsmMultiply(12, 12));
    
    char src[] = "Hello World";
    char dest[32] = {0};
    AsmMemcpy(dest, src, sizeof(src));
    printf("Copied: %s\n", dest);
    
    return 0;
}
; asmmodule.asm
.386
.model flat, C           ; C调用约定
option casemap:none

.code

; int __cdecl AsmAdd(int a, int b)
AsmAdd PROC
    push ebp
    mov ebp, esp
    
    mov eax, [ebp+8]        ; a
    add eax, [ebp+12]       ; + b
    
    pop ebp
    ret                     ; cdecl: 调用者清栈
AsmAdd ENDP

; int __stdcall AsmMultiply(int a, int b)
; 注意: stdcall需要用@后缀或在.model中指定
AsmMultiply PROC
    push ebp
    mov ebp, esp
    
    mov eax, [ebp+8]
    imul eax, [ebp+12]
    
    pop ebp
    ret 8                   ; stdcall: 被调用者清栈
AsmMultiply ENDP

; void __cdecl AsmMemcpy(void* dest, const void* src, size_t n)
AsmMemcpy PROC
    push ebp
    mov ebp, esp
    push esi
    push edi
    
    mov edi, [ebp+8]        ; dest
    mov esi, [ebp+12]       ; src
    mov ecx, [ebp+16]       ; n
    
    cld
    rep movsb
    
    pop edi
    pop esi
    pop ebp
    ret
AsmMemcpy ENDP

END

5.4、示例4:C++与汇编混合

// cppmodule.cpp
#include <iostream>

// 使用extern "C"避免名称修饰
extern "C" {
    int AsmFunction(int x);
    void CallCppFromAsm(int value);
}

// C++函数,供汇编调用
void CallCppFromAsm(int value) {
    std::cout << "Called from ASM with: " << value << std::endl;
}

class Calculator {
public:
    int Add(int a, int b) {
        int result;
        __asm {
            mov eax, a
            add eax, b
            mov result, eax
        }
        return result;
    }
    
    // 使用this指针
    int GetValue() {
        int* pValue = &m_value;
        int result;
        __asm {
            mov ecx, pValue
            mov eax, [ecx]
            mov result, eax
        }
        return result;
    }
    
private:
    int m_value = 42;
};

int main() {
    Calculator calc;
    std::cout << "Add: " << calc.Add(10, 20) << std::endl;
    std::cout << "Value: " << calc.GetValue() << std::endl;
    return 0;
}

5.5、示例5:x64混合编程方案

// x64不支持内联汇编,使用单独.asm文件
// main_x64.c

#include <stdio.h>
#include <intrin.h>  // Intrinsic函数

// 声明外部汇编函数
extern __int64 AsmAdd64(__int64 a, __int64 b);
extern void AsmMemset64(void* dest, int value, size_t count);

// 使用Intrinsic代替内联汇编
void IntrinsicExample() {
    // 读取TSC
    unsigned __int64 tsc = __rdtsc();
    printf("TSC: %llu\n", tsc);
    
    // CPUID
    int cpuInfo[4];
    __cpuid(cpuInfo, 0);
    printf("CPUID: EAX=%08X\n", cpuInfo[0]);
    
    // 字节交换
    unsigned short val = 0x1234;
    val = _byteswap_ushort(val);
    printf("Swapped: %04X\n", val);
    
    // 位扫描
    unsigned long index;
    unsigned char found = _BitScanForward(&index, 0x80);
    if (found) printf("First bit at: %lu\n", index);
}

int main() {
    IntrinsicExample();
    
    printf("AsmAdd64: %lld\n", AsmAdd64(100, 200));
    
    return 0;
}
; x64_module.asm
; ml64 /c x64_module.asm

.code

; __int64 AsmAdd64(__int64 a, __int64 b)
; RCX = a, RDX = b
AsmAdd64 PROC
    mov rax, rcx
    add rax, rdx
    ret
AsmAdd64 ENDP

; void AsmMemset64(void* dest, int value, size_t count)
; RCX = dest, EDX = value, R8 = count
AsmMemset64 PROC
    push rdi
    
    mov rdi, rcx            ; dest
    mov eax, edx            ; value (BYTE)
    mov rcx, r8             ; count
    cld
    rep stosb
    
    pop rdi
    ret
AsmMemset64 ENDP

END

6、课后作业

  1. 内联汇编练习:用内联汇编实现常用的位操作函数
  2. 混合链接:创建一个C/汇编混合项目,实现字符串处理函数
  3. x64适配:将x86内联汇编代码改写为x64单独模块
  4. 性能对比:比较内联汇编和Intrinsic的性能差异