原创 宏、汇编、C和C++混合编程

2007-9-16 12:14 2733 4 4 分类: 工程师职场

宏、汇编、C和C++混合编程
*************************************
* 第十三讲 宏、汇编、C和C++混合编程 *
*************************************
    2007/03/14  asdjf@163.com  www.armecos.com
   
    在嵌入式开发时,有时需要优化代码,提高执行效率,此时要用到内嵌汇编技术;有时程序比较复杂,采用面向对象技术可以提高代码复用率和可靠性;有时一些宏的使用技巧可以简化代码。通常,在一个项目中,这些需求是同时存在的,因此,编译器需要更多信息了解程序员的意图。混合编程可以充分发挥各种语言的优势,综合利用能取得显著效果。
   
    -------------------
    | 汇编和C混合编程 |
    -------------------
   
    在C中嵌入汇编的格式为:
    asm(“汇编语句”
        :输出寄存器
        :输入寄存器
        :会被修改的寄存器);
    其中,“汇编语句”是程序员写汇编指令的地方;“输出寄存器”表示当这段嵌入汇编执行之后,哪些寄存器用于存放输出数据。这些寄存器会分别对应一个C语言表达式或一个内存地址;“输入寄存器”表示在开始执行汇编代码时,这里指定的一些寄存器中应存放的输入值,它们也分别对应着一个C变量或常数值。“会被修改的寄存器”似乎很古怪,不过在gcc知道程序员拿这些寄存器做什么后,确实能够对优化操作带来帮助。下面举例说明:
   
    01    #define get_seg_byte(seg, addr) \
    02    ({ \
    03    register char __res;
    04    __asm__("push %%fs; \
    05             mov %%ax, %%fs; \
    06             movb %%fs:%2, %%al; \
    07             pop %%fs" \
    08             :"=a"(__res) \
    09             :""(seg), "m"(*(addr))); \
    10    __res;})
   
    这段10行代码定义了一个嵌入汇编语言宏函数。用圆括号括住的组合语句(花括号中的语句)可以作为表达式使用,第10行变量__res是该表达式的输出值。
    宏语句要在一行上定义,因此使用“\”将这些语句连成一行。宏的名字是get_seg_byte(seg, addr)。第3行定义寄存器变量__res。第4行的__asm__表示嵌入汇编语句的开始。4-7行是AT&T格式的汇编语句。
    第8行是输出寄存器,其含义是此段代码结束后将eax所代表的寄存器的值放入__res变量中,作为本函数的输出值,"=a"中的"a"称为加载代码,"="表示这是输出寄存器。
    第9行表示此段代码开始运行时将seg放到eax寄存器中,""表示使用与上面同个位置的输出相同的寄存器。"m"表示使用一个内存偏移地址值。
    为了在上面的汇编语句中使用该地址值,嵌入汇编程序规定把输出和输入寄存器统一按顺序编号,顺序是从输出寄存器序列从左到右从上到下以"%0"开始,分别记为%0、%1...%9。因此,输出寄存器的编号是%0(这里只有一个输出寄存器),输入寄存器前一部分(""(seg))的编号是%1,而后部的编号是%2。上面第6行上的%2即代表(*(addr))这个内存偏移量。
    第4行代码的作用是将fs段寄存器的内容入栈;第5行将eax中的段值赋给fs段寄存器;第6行是把fs*(addr))所指定的字节放入al寄存器中。当执行完汇编语句后,输出寄存器eax的值将被放入__res,作为该宏函数的返回值。
    这段程序中,seg代表一指定的内存段值,而addr表示一内存偏移地址量。该宏函数的功能是从指定段和偏移值的内存地址处取一个字节。
    表1是一些可能会用到的寄存器加载代码及其具体的含义。
   
    表1 常用寄存器加载代码说明
    ----------------------------------------------------------------------------------
    | 代码 |              说明              || 代码 |              说明              |
    ----------------------------------------------------------------------------------
    |  a   | 使用寄存器eax                  ||  m   | 使用内存地址                   |
    ----------------------------------------------------------------------------------
    |  b   | 使用寄存器ebx                  ||  o   | 使用内存地址并可以加偏移量     |
    ----------------------------------------------------------------------------------
    |  c   | 使用寄存器ecx                  ||  I   | 使用常数0~31                   |
    ----------------------------------------------------------------------------------
    |  d   | 使用寄存器edx                  ||  J   | 使用常数0~63                   |
    ----------------------------------------------------------------------------------
    |  S   | 使用esi                        ||  K   | 使用常数0~255                  |
    ----------------------------------------------------------------------------------
    |  D   | 使用edi                        ||  L   | 使用常数0~65535                |
    ----------------------------------------------------------------------------------
    |  q   | 使用动态字节可寻址寄存器(eax、 ||  M   | 使用常数0~3                    |
    |      | ebx、ecx或edx)                 ||      |                                |
    ----------------------------------------------------------------------------------
    |  r   | 使用任意动态分配的寄存器       ||  N   | 使用1字节常数(0~255)           |
    ----------------------------------------------------------------------------------
    |  g   | 使用通用有效的地址即可(eax、   ||  O   | 使用常数0~31                   |
    |      | ebx、ecx、edx或内存变量)       ||      |                                |
    ----------------------------------------------------------------------------------
    |  A   | 使用eax与edx联合(64位)         ||      |                                |
    ----------------------------------------------------------------------------------
   
    再看下一个例子:
   
    01    asm("cld\n\t"
    02        "rep\n\t"
    03        "stol"
    04        :/*没有输出寄存器*/
    05        :"c"(count - 1), "a"(fill_value), "D"(dest)
    06        :"%ecx", "%edi");
   
    1-3行是通常的汇编语句,用以清方向位,重复保存值。
    第4行说明此段嵌入汇编没有用到输出寄存器。
    第5行的含义是:将count-1的值加载到ecx寄存器中(加载代码是"c"),fill_value加载到eax中,dest放到edi中。为什么要让gcc编译器去做这样的寄存器值的加载,而不让我们自己做呢?因为这样有利于gcc进行某些优化工作。例如:fill_value值可能已经在eax中了,如果是在一个循环语句中的话,gcc就可能在整个循环中保留eax,这样就可以在每次循环中少用一个movl语句。
    第6行告诉gcc这些寄存器中的值已经改变了。这些信息有利于gcc优化操作。
   
    下面的例子不是让程序员自己指定哪个变量使用哪个寄存器,而是让gcc为程序员选择。
   
    01    asm("lea;(%1, %1, 4), %0"
    02        :"=r"(y)
    03        :"o"(x));
   
    这个例子可以非常快地将x乘5。其中"%0","%1"是指gcc自动分配的寄存器。这里"%1"代表输入值x要存入的寄存器,"%0"表示输出值寄存器。输出寄存器代码前一定要加等于号。如果输入寄存器的代码是0或为空时,则说明使用与相应输出一样的寄存器。所以,如果gcc将r指定为eax的话,那么上面汇编语句的含义即为:"leal (eax, eax, 4), eax"。
    注意:在执行代码时,如果不希望汇编语句被gcc优化而改变位置,就需要在asm符号后添加volatile关键词:asm volatile(...);
          或者更详细地说明为:__asm__ __volatile__(...)。
   
    ------------------
    | C和C++混合编程 |
    ------------------


    作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:
   
    void foo( int x, int y );


  该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。


  _foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。


按以下方式写C++头文件即可。


#ifndef __INC_ecos
#define __INC_ecos


#ifdef __cplusplus
extern "C" {
#endif


/*在下面位置添加需要的头文件内容...*/


#ifdef __cplusplus
}
#endif


#endif /* __INC_ecos */



(1)C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C"但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。


/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif
/* c语言实现文件:cExample.c */
#include "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++实现文件,调用add:cppFile.cpp
extern "C"
{
#include "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}


(2)如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。


//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++实现文件 cppExample.cpp
#include "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C实现文件 cFile.c
/* 这样会编译出错:#include "cExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}


    -----------------------
    | 宏中"#"和"##"的用法 |
    -----------------------


一、一般用法
我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起.
用法:
#i nclude
#i nclude
using namespace std;


#define STR(s)     #s
#define CONS(a,b)  int(a##e##b)


int main()
{
    printf(STR(vck));           // 输出字符串"vck"
    printf("%d\n", CONS(2,3));  // 2e3 输出:2000
    return 0;
}


二、当宏参数是另一个宏的时候
需要注意的是凡宏定义里有用'#'或'##'的地方宏参数是不会再展开.


1, 非'#'和'##'的情况
#define TOW      (2)
#define MUL(a,b) (a*b)


printf("%d*%d=%d\n", TOW, TOW, MUL(TOW,TOW));
这行的宏会被展开为:
printf("%d*%d=%d\n", (2), (2), ((2)*(2)));
MUL里的参数TOW会被展开为(2).


2, 当有'#'或'##'的时候
#define A          (2)
#define STR(s)     #s
#define CONS(a,b)  int(a##e##b)


printf("int max: %s\n",  STR(INT_MAX));    // INT_MAX #i nclude
这行会被展开为:
printf("int max: %s\n", "INT_MAX");


printf("%s\n", CONS(A, A));               // compile error
这一行则是:
printf("%s\n", int(AeA));


INT_MAX和A都不会再被展开, 然而解决这个问题的方法很简单. 加多一层中间转换宏.
加这层宏的用意是把所有宏的参数在这层里全部展开, 那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数.


#define A           (2)
#define _STR(s)     #s
#define STR(s)      _STR(s)          // 转换宏
#define _CONS(a,b)  int(a##e##b)
#define CONS(a,b)   _CONS(a,b)       // 转换宏


printf("int max: %s\n", STR(INT_MAX));          // INT_MAX,int型的最大值,为一个变量 #i nclude
输出为: int max: 0x7fffffff
STR(INT_MAX) -->  _STR(0x7fffffff) 然后再转换成字符串;


printf("%d\n", CONS(A, A));
输出为:200
CONS(A, A)  -->  _CONS((2), (2))  --> int((2)e(2))


三、'#'和'##'的一些应用特例
1、合并匿名变量名
#define  ___ANONYMOUS1(type, var, line)  type  var##line
#define  __ANONYMOUS0(type, line)  ___ANONYMOUS1(type, _anonymous, line)
#define  ANONYMOUS(type)  __ANONYMOUS0(type, __LINE__)
例:ANONYMOUS(static int);  即: static int _anonymous70;  70表示该行行号;
第一层:ANONYMOUS(static int);  -->  __ANONYMOUS0(static int, __LINE__);
第二层:                        -->  ___ANONYMOUS1(static int, _anonymous, 70);
第三层:                        -->  static int  _anonymous70;
即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;


2、填充结构
#define  FILL(a)   {a, #a}


enum IDD{OPEN, CLOSE};
typedef struct MSG{
  IDD id;
  const char * msg;
}MSG;


MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相当于:
MSG _msg[] = {{OPEN, "OPEN"},
              {CLOSE, "CLOSE"}};


3、记录文件名
#define  _GET_FILE_NAME(f)   #f
#define  GET_FILE_NAME(f)    _GET_FILE_NAME(f)
static char  FILE_NAME[] = GET_FILE_NAME(__FILE__);


4、得到一个数值类型所对应的字符串缓冲大小
#define  _TYPE_BUF_SIZE(type)  sizeof #type
#define  TYPE_BUF_SIZE(type)   _TYPE_BUF_SIZE(type)
char  buf[TYPE_BUF_SIZE(INT_MAX)];
     -->  char  buf[_TYPE_BUF_SIZE(0x7fffffff)];
     -->  char  buf[sizeof "0x7fffffff"];
这里相当于:
char  buf[11];


 


#pragma hdrstop
#include
#include
using namespace std;
using std::cin;
using std::cout;
//---------------------------------------------------------------------------


#pragma argsused
int main(int argc, char* argv[])
{
    vector ivec(5,20);
 int i;


 for (i=0; i!=5; i++)
 {
  ivec.push_back(i);
 }


    vector::iterator j= ivec.begin();


 while ( j !=ivec.end() )
 {
  cout<<*j++< }
    cout<<"End"<    cin>>i;
 return 0;
}

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
我要评论
0
4
关闭 站长推荐上一条 /3 下一条