• C语言/C++调用汇编语言函数

    为设备驱动器和嵌入式系统编码的程序员常常需要把 C/C++ 模块与用汇编语言编写的专门代码集成起来。汇编语言特别适合于直接硬件访问、位映射,以及对寄存器和 CPU 状态标识进行底层访问。

    整个应用程序都用汇编语言编写是很乏味的,比较有用的方法是,用 C/C++ 编写主程序,而那些不太好用 C 编写的代码则用汇编语言。现在来讨论一下从 32 位 C/ C++ 程序调用汇编程序的一些标准要求。

    C/C++ 程序从右到左传递参数,与参数列表的顺序一致。函数返回后,主调程序负责将堆栈恢复到调用前的状态。这可以采用两种方法:一种是将堆栈指针加上一个数值,该值等于参数大小;还有一种是从堆栈中弹出足够多的数。

    在汇编源代码中,需要在 .MODEL 伪指令中指定 C 调用规范,并为外部 C/C++ 程序调用的每个过程创建原型。示例如下:

    .586
    .model flat,C
    IndexOf PROTO,
        srchVal:DWORD, arrayPtr:PTR DWORD, count:DWORD

    函数声明

    在 C 程序中,声明外部汇编过程时要使用 extern 限定符。比如,下面的语句声明了 IndexOf:

    extern long IndexOf(long n, long array[], unsigned count);

    如果过程会被 C++ 程序调用,就要添加“C”限定符,以防止 C++ 的名称修饰:

    extern "C" long IndexOf(long n, long array[], unsigned count);

    名称修饰 (name decoration) 是一种标准 C++ 编译技术,通过添加字符来修改函数名,添加的字符指明了每个函数参数的确切类型。任何支持函数重载(多个函数有相同的函数名、不同的参数列表)的语言都需要这个技术。

    从汇编语言程序员的角度来看,名称修饰存在的问题是:C++ 编译器让链接器去找的是修饰过的名称,而不是生成可执行文件时的原始名称。

    IndexOf 示例

    现在新建一个简单汇编函数,对数组实现线性搜索,找到与样本整数匹配的第一个实例。如果搜索成功,则返回匹配元素的索引位置;否则,返回 -1。该函数将被 C++ 程序调用。在 C++ 中,编写程序段如下:

    long IndexOf(long searchVal, long array[], unsigned count)
    {
        for(unsigned i = 0; i < count; i++) {
            if(array[i] == searchVal )
                return i;
        }
        return -1;
    }

    参数包括:希望被找到的数值、一个数组指针,以及数组大小。

    用汇编语言编写该函数显然是很容易的。编写好的汇编代码放入自己的源代码文件 IndexOf.asm。这个文件将被编译为目标代码文件 IndexOf.obj。使用 Visual Studio 实现主调 C++ 程序与汇编模块的编译和链接。C++ 项目将用 Win32 控制台作为其输出类型,虽然也没有理由不让它成为图形应用程序。

    下面为 IndexOf 模块的源代码清单。

    ;IndexOf 函数    (IndexOf . asm)
    .586
    .model flat,C
    IndexOf PROTO,
        srchVal:DWORD, arrayPtr:PTR DWORD, count:DWORD
    
    .code
    ;-------------------------------------------
    IndexOf PROC USES ecx esi edi,
        srchVal:DWORD, arrayPtr:PTR DWORD, count:DWORD
    ;
    ;对 32 位整数数组执行线性搜索,
    ;寻找指定数值。如果发现匹配数值,
    ;用EAX返回该数值的索引位置;
    ;否则,EAX 返回 -1。
    ;-------------------------------------------
        NOT_FOUND = -1
    
        mov    eax, srchVal      ;搜索数值
        mov    ecx, count        ;数组大小
        mov    esi, arrayPtr     ;数组指针
        mov    edi, 0            ;索引
    
    L1:cmp [esi+edi*4],eax
        je found
        inc edi
        loop L1
    
    notFound:
        mov eax,NOT_FOUND
        jmp short exit
    
    found:
        mov eax,edi
    
    exit:
        ret
    IndexOf ENDP
    END

    首先,注意到用于测试循环的汇编代码 25〜28 行,虽然代码量小,但是高效。对要执行很多次的循环,应试图使其循环体内的指令条数尽可能少:

    L1: cmp [esi+edi*4],eax
        je found
        inc edi
        loop L1

    如果找到匹配项,程序跳转到 34 行,将 EDI 复制到 EAX,该寄存器用于存放函数返回值。在搜索期间,EDI 为当前索引位置。

    found:
        mov eax,edi

    如果没有找到匹配项,则把 -1 赋值给 EAX 并返回:

    notFound:
        mov eax,NOT_FOUND
        jmp short exit

    下面为主调 C++ 程序清单。

    #include <iostream>
    #include <time.h>
    #include "indexof.h"
    using namespace std;
    
    int main()  {
        // 用伪随机数填充数组
        const unsigned ARRAY_SIZE = 100000;
        const unsigned LOOP_SIZE = 100000;
        char* boolstr[] = {"false","true"};
    
        long array[ARRAY_SIZE];
        for(unsigned i = 0; i < ARRAY_SIZE; i++)
         array[i] = rand();
    
        long searchVal;
        time_t startTime, endTime;
        cout << "Enter an integer value to find: ";
        cin >> searchVal;
        cout << "Please wait...\n";
    
        // 测试汇编函数
        time( &startTime );
        long count = 0;
    
        for( unsigned n = 0; n < LOOP_SIZE; n++)
             count = IndexOf( searchVal, array, ARRAY_SIZE );
    
        bool found = count != -1;
    
        time( &endTime );
        cout << "Elapsed ASM time: " << long(endTime - startTime)
              << " seconds. Found = " << boolstr[found] << endl;
    
        return 0;
    }

    首先,用伪随机数值对数组进行初始化:

    long array [ARRAY_SIZE];
    for(unsigned i = 0; i < ARRAY_SIZE; i++)
        array[i] = rand();

    18〜19 行提示用户输入在数组中搜索的数值:

    cout << "Enter an integer value to find:";
    cin >> searchVal;

    23 行调用 C 链接库的 time 函数(在 time.h 中),把从 1970 年 1 月 1 日起已经过的秒数保存到变量 startTime:

    time(&startTime);

    26 和 27 行按照 LOOP_SIZE 的值 (100 000),反复执行相同的搜索:

    for(unsigned n = 0; n < LOOP_SIZE; n++)
        count = IndexOf(searchVal, array, ARRAY_SIZE);

    由于数组大小也约为 100 000,因此执行步骤的总数可以多达 100 000 x 100 000,或 100 亿。31〜33 行再次检查时间,并显示循环运行所耗的秒数:

    time(&endTime);
    cout << "Elapsed ASM time: " << long(endTime - startTime)
         << "seconds. Found = " << boolstr[found] << endl;

    在高速计算机上测试时,循环执行时间为 6 秒。对 100 亿次迭代而言,这个时间不算多,每秒约有 16.7 亿次迭代。重要的是,需要意识到程序重复过程调用的开销(参数入栈,执行 CALL 和 RET 指令)也是 100 000 次。过程调用导致了相当多的额外处理。

更多...

加载中...