网站公告列表

  没有公告

加入收藏
设为首页
联系站长
您现在的位置: 网络学院 >> 程序设计 >> VC编程 >> 文章正文
  第十三讲 宏、汇编、C和C++混合编程zz            【字体:
第十三讲 宏、汇编、C和C++混合编程zz
作者:佚名    文章来源:不详    点击数:    更新时间:2007-9-12    

发信人: gdtyy (gdtyy), 信区: Embedded
标  题: 第十三讲 宏、汇编、C和C++混合编程
发信站: 水木社区 (Mon Jun 25 23:37:23 2007), 站内

*************************************
* 第十三讲 宏、汇编、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<cstdio>
#i nclude<climits>
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<climits>
这行会被展开为:
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<climits>
输出为: 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];

--

※ 来源:·水木社区 http://newsmth.net·[FROM: 61.149.56.*]
 




本文来源:http://blog.csdn.net/autoca/archive/2007/08/30/1765959.aspx
站内文章搜索 高级搜索
文章录入:admin    责任编辑:admin 
  • 上一篇文章:

  • 下一篇文章:
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
    最新热点 最新推荐 相关文章
     directx 图形接口指南(…
     win2k下的api函数的拦截
     用crypto  api  实现公钥…
     根据别人的md5源码封装的…
     vc中使用gdi+合并jpg图片
     document/view的交互 --…
     windows下的函数hook技术
     windows api函数大全一
     用vc 6.0实现串行通信的…
     vc++技术内幕(第四版)…
  • 第十二讲 多目录下makefile的…

  • 第十四章 自定义JSP标签(书…

  • 第十六章 Struts和MVC设计模…

  • 第十二章    回到C/C++的王国

  • JDK6的新特性之十三:JTable的…

  • TIJ阅读笔记(第十二章)

  • RMI规范--第十章

  • 第三讲 Java语言中的面向对…

  • 第十三讲:关于.NET组件

  • 【Java基础】第十一讲 JDBC编…

  •   网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)
    网络学院©2007 www.23book.net
    为您提供web编程,vb编程,vc编程,服务器架设管理,数据库设计等方面的知识 站长:David