深入理解C语言(汇编语言程序员角度)
阅读原文时间:2021年04月21日阅读:1

转自:http://blog.sina.com.cn/s/blog_16696ec8f0102wq2d.html

一、比较C与汇编的语言要素(可有表格、例子等)

1.数据类型比较:所有数据类型(数与信息、无符号、有符号等;包括布尔类型、    指针、多维数组、结构、联合、自定义类型等;       类class(选作可加分)

汇编语言:

BYTE

8位无符号整数

1字节

SBYTE

8位有符号整数

1字节

WORD

16位无符号整数

2字节

SWORD

16位有符号整数

2字节

DWORD

32位无符号整数

4字节

SDWORD

32位有符号整数

4字节

FWORD

48位整数

6字节

QWORD

64位整数

8字节

TBYTE

80位整数

10字节

REAL4

32位IEEE短实数

4字节

REAL8

64位IEEE长实数

8字节

REAL10

80位IEEE扩展精度实数

10字节

字符串

通常每个字节存储一个字符

若干个字节

TYPE

自定义类型

若干个字节

C语言:

short

短整型

2字节

Int

整型

4字节

Long

长整型

8字节

Float

单精度

4字节

Double

双精度

8字节

Char

字符型

1字节

Array

数组

Struct

结构体

Union

共用体

Enum

枚举类型

Point

指针类型

Void

空类型

Bool

1字节

综合比较:C语言与汇编语言一般的数据类型都差不多,不过C语言当中没有对应三字节或是五字节的的变量类型,要么是int型,要么是long型,所以每次申请必须是固定的字节数,势必造成内存使用上的浪费,而内存使用效率不高同时也会影响到整个程序的整体效率。而大部分的汇编语言没有这样的语法,在伪指令的帮助下,汇编语言程序可以使用任意字节的变量,当然处理起来比C语言麻烦得多,最终还是一个字节一个字节地拼接处理,不过这些最终会由编译器来完成。

汇编语言采用不同的后缀区分:B:二进制数; O:八进制数; D:十进制数; H:十六进制数;当一个数值后面没有后缀的时候,默认为十进制数;字符串常数是用一对单引号('')括起 来的一串字符。

   另外一个区别就是指针类型了,汇编语言的寻址方式与C语言中的指针是类似的。汇编语言的寻址方式与C语言中的指针都是寻找数据的方法。指针就是存了变量的地址,寻址方式就是得到保存变量的地址。

2.常量(包括符号常量)、常量表达式、变量(寄存器是特殊的全局变量、全局变量、局部变量、静态变量)、变量表达式、条件表达式、关系表达式

C语言

汇编语言

常量

1.      宏定义:#define N 3

2.      给变量初始化赋值:

Contest int a=1; contest char c=’a’;

1.      AA  EQU  256

2.      EMP = 5

变量

Int a=1;char a=’S’;等

[变量名] 数据定义伪指令 初始值(可以有多个)

如:count DWORD 100

Arr BYTE 1,2,3,4

C语言提供了34种运算符,这其中包括算术运算符,增1,减1运算符以及强制类型转换运算符等等。例如+、-、*、/、-(取反)、++,--。运算符有优先级,在组成算术表达式时,编译器将按照预先预定好的优先级对表达式进行运算。C语言还提供了宏的功能,包括宏替换和const

汇编语言:

算数运算操作符: +、-、*、/、MOD,等;取模运算MOD是取两数相除的余数; 

逻辑运算操作符: AND(逻辑与)、OR(逻辑或)、NOT(逻辑非)、XOR(逻辑

异或) 

注意:逻辑运算符同时又可以是逻辑运算指令的指令码,只有当它们出现在指 

令的操作数部分时,才是操作符;例如: 

ADD AL,0CH ADD 0FH  ;第一个ADD是指令码,第二个ADD是操作符; 

关系运算操作符: EQ(相等)、NE(不等)、LT(小于)、GT(大于)、LE(小于等于)、GE(大于等于); 

汇编语言中的表达式不能单独构成语句,只能是语句的组成部分

3.赋初值(初始化)、赋值语句(全局、局部变量)、算术运算(整数、浮点、有无符号)及逻辑运算,分支转移语句(含多分支Switch)、循环语句(3种)

C语言:

(1)    C语言的算术运算和表达式:C语言提供了三十多种运算符,其中包括算术运算符,加一,减一运算符以及强制类型转换运算符等等。例如+ 、-、*、/、-(取反)、++、--。运算符有优先级在组成算术表达式时,编译器会按照预先预定好的优先级对表达式进行运算。

(2)    选择控制结构:C语言的选择控制结构有IF()语句,多选分支的if  else语句,switch()语句等

(3)    循环控制语句:C语言的循环控制语句有for()语句,while()语句,do()while()语句;辅助语句有continue和break语句。

汇编语言:

汇编运算由操作数和操作符组成:算术运算操作符由+、-、*、/、MOD(取余)等;逻辑运算符:AND  OR  NOT  XOR 逻辑运算符同时又可以是逻辑运算指令的指令码,只有当它们出现在指令的操作数部分时,才是操作符。例如

ADD AL,0CH   ADD 0FH;第一个ADD是指令码,第二个ADD是操作符;

关系运算符:EQ(相等)NE(不相等)LT(小于)GT(大于)LE(小于等于)GE(大于等于)  

汇编语言中的表达式不能单独构成语句,只能是语句的组成部分。

语句中表达式的求值不是在语句执行时完成的,而是在对源程序进行汇编链接是完成的,所以,语句中各表达式的值必须在汇编或链接是就是确定的,也就是说,表达式中各标识符的值在汇编或链接时就应该是确定的。
4.子程序(函数)的声明、调用与返回、参数传递、返回值,传值与传地址

声明 定义

调用与返回

参数传递

传值与传地址

C语言(函数)

返回值类型 函数名(类型 形参1,类型形参2,… )

{(这里面为函数体)

声明语句序列

可执行语句序列

}

函数(如main函数)调用其他函数时,必须提供实参给被调用的函数。

如果没有函数返回值,就用void定义返回值类型;

若有函数返回值,则用相应的返回值类型来定义,有返回值的函数必须要有return语句。

主调函数把实参的值复制给被调函数的形参的过程叫参数传递;形参是函数的入口,形参表里的形参就相当于运算的操作数,函数的返回值就相当于运算的结果

传值

汇编语言(子程序)

纯纯模式-标号模式

F:MOV EAX,1

   …..

   RET

PROC/ENDP模式(如MAIN)

PROTO/INVOKE模式(如printf等先声明后使用)

在主程序里用CALL P调用子程序P,在子程序里是用RET返回子程序;

调用和返回是通过堆栈来实现的:把CALL的下一条指令的地址压栈

1.      寄存器传递参数:占用寄存器,主程序要对参数寄存器赋值,双方都知道约定的寄存器是哪几个;速度最快;比较简洁

2.      变量传递参数:双方都知道参数是哪些全局变量;主程序,子程序都简洁

3.      堆栈传递函数:堆栈框架的使用

传地址

可以在段内调用,也可以跨段调用

5.伪指令与宏:宏声明、调用与返回,参数传递。

汇编语言:

伪指令:

   伪指令是内嵌在源程序代码中,由汇编器识别并执行相应动作的命令,伪指令在程序运行中并不执行,伪指令可以用来定义变量,宏及过程。常用的伪指令有:mov  .data  .code  .stack  invoke   jmp  call等等

宏:

宏是具有宏名的一段汇编语言序列,宏是汇编语言的一个特点,它是与子程序类似又独具特色的另一种化简源程序的方法。

宏的声明:宏可以直接在程序的头部(开始)定义,或者也可以放在单独的文本文件中,通过INCLUDE伪指令把宏定义复制(插入到)源程序中。

宏的定义:    宏名 macro [形参表]

             宏定义体

           endm

例如:

mainbegin MACRO      ;;定义名为mainbegin的宏,无参数

mov AX,1;;宏定义体

mov AH, 4ch

ENDM  ;;宏定义结束

mained MACRO retnum ;;带有形参retnum

mov AL,retnum    ;;宏定义中实用参数

mov AH,4ch

int 21h

ENDM

宏的调用:

start: mainbegin ;宏调用,建立DS内容

mainend 0;宏调用

end start

宏调用的实质是在汇编过程中进行宏展开

宏展开的具体过程是:宏是在汇编器的预处理阶段展开的。当汇编程序扫描源程序遇到已有定义的宏调用时,即用相应的宏定义体来取代源程序的宏指令,同时用位置匹配的实参对形参进行取代。

宏展开:在汇编时,用宏定义体的代码序列代替宏指令的过程。

宏的参数:

宏定义时,可以无参数,例如mainbegin  可以带有一个参数,例如mained

也可以有多个参数;

参数可以是常数,变量,存储单元,指令(操作码)或它们的一部分,也可以是表达式。宏的参数没有类型,因此预处理器并不检查实参的类型是否与形参的类型匹配。如果实际上不匹配,那么这类错误在宏展开后由汇编器检查。

宏参数传递:调用宏时的每个实际参数都是都是一个文本值,该值将替换宏定义中的形参,实际参数的顺序必须与形参的定义顺序相同。当实参的个数少于形参的个数时,那么未传递的参数为空,如果实参的个数多余形参的个数时,汇编器会产生警告。

宏定义体可以是任何合法的汇编语句,既可以是硬指令序列,又可以是伪指令序列。

与宏有关的伪指令有:LOCAL(局部标识符列表),PURGE 宏名表(删除伪指令),EXITM(退出伪指令)

一个宏的例子:

chr$ MACRO any_text : VARARG; 目前先用, 宏时讲

    LOCAL txtname

    .DATA

    txtname db any_text, 0

    ALIGN 4

    .CODE

    EXITM

ENDM

C语言的伪指令宏:

   宏常量也称符号常量,是指用一个标识符来来表示的常量。宏常量是由宏定义编译预处理命令来定义的。宏定义的一般形式为:  #define 标识符(宏名-常字母全部大写) 字符串   其作用是用#define编译预处理指令定义一个标识符和一个字符串,凡在源程序中发现该标识符时,都用其后特定的字符串来替换。

跟汇编语言一样,宏替换时不做任何语法检查。

   值得注意的是,宏名与字符串之间可以有多个空白符号,但无需加等号,且字符串后只能以换行符终止,一般不以分号结尾。若字符串后加分号,则宏替换时会连同分号一起替换。

  
6.类:类定义、成员变量与初始化、成员函数、继承、派生、友元、多态、重载、公共/私有,构造/析构;容器、模板。(选做,可加分)
7.向外部提供变量或子程序 ,使用外部变量和子程序

C语言调用汇编语言的变量和子程序通过  extern ‘C’+调用的内容,例如:

C语言为汇编语言提供的变量和方法放在  extern ‘C’{}中进行实现,例如:

汇编语言调用C语言的变量和方法通过extern C   PROTO C:ptr  例如:

汇编语言为C语言提供的变量和子程序在.data中  例如:

8.头文件:.inc 与.h的内容区别,格式区别

一般用.h
.inc很少用,二者没什么差别
比如include\
的库函数头文件定义就都是.h

补充:具体来说:
*.inc   文件   多是存放配置文件的。    
*.h   文件   多是变量声明以及函数的声明

9.源程序模板:主要区别(包括注释)

汇编语言源程序模板:

TITLE Program Template    (Template)

;程序的描述:

;作者:

;创建日期:

;修改:

;日期:          修改者:

INCLUDE Irvine32.INC  ;头文件

.DATA

;(在此插入变量)

.CODE

Mian PROC

;(在此插入可执行代码)

exit

main ENDP

;(在此插入其他子程序)

END main

C语言源程序模板:

#include

int main()

{

return o;

}

10. 第一条语句标识的区别

mainC语言)

main PROC(汇编语言)

一、main()函数的形式

在最新的 C99 标准中,只有以下两种定义方式是正确的:

int main( void )  
{
    …
    return 0;
}
int main( int argc, char *argv[] ) 
{
    …
    return 0;
}
int指明了main()函数的返回类型,函数名后面的圆括号一般包含传递给函数的信息。void表示没有给函数传递参数。

二、main()函数的返回值

从前面我们知道main()函数的返回值类型是int型的,而程序最后的 return 0; 正与之遥相呼应,0就是main()函数的返回值。那么这个0返回到那里呢?返回给操作系统,表示程序正常退出。因为return语句通常写在程序的最后,不管返回什么值,只要到达这一步,说明程序已经运行完毕。而return的作用不仅在于返回一个值,还在于结束函数。

三、main()函数的参数

C编译器允许main()函数没有参数,或者有两个参数(有些实现允许更多的参数,但这只是对标准的扩展)。这两个参数,一个是int类型,一个是字符串类型。第一个参数是命令行中的字符串数。按照惯例(但不是必须的),这个int参数被称为argc(argument count)。第二个参数是一个指向字符串的指针数组。命令行中的每个字符串被存储到内存中,并且分配一个指针指向它。按照惯例,这个指针数组被称为argv(argument value)。系统使用空格把各个字符串格开。一般情况下,把程序本身的名字赋值给argv[0],接着,把最后的第一个字符串赋给argv[1],等等。

PROC 伪指令用来标识一个过程的开始,过程的名字是main(main过程是汇编程序的主过程)

该过程又被称之为启动过程,以exit语句结束

二:自编C程序,对其执行文件反汇编,深入分析并阐述C语言各要素的底层实现(提供C、ASM、内存、寄存器等截屏)--以32位编译器为例

C语言源程序为:

#include

#include

#include

//调用外部程序的东东在这儿声明

extern "C" char *vasm;        //传过来的数组必须是地址,而不是数组的内容

extern "C" int  aaaa;

extern "C" int  fasm(char *a, int d);

//为外部程序提供的东东在这儿声明

extern  "C"

{

    char *vc = "C的变量欢迎你";

    int  cccc = 0xcccc;

    void fc(char *s, int d);

}

void fc(char *s, int d)

{

    printf("====现在是C语言的代码在执行====\n");

    printf("    显示参数的内容:  %s  X \n", s, d);

    printf("==== C 语言程序现在执行完毕====\n");

}

typedef struct sample

{

    char m1;

    int m2;

    char m3;

}SAMPLE;

int add(int a, int b)  //计算两个整数的和

{

    return (a+b);

}

int main()

{

    int sum;

    fc(vc, cccc);

    fc(vasm, aaaa);

    fasm(vasm, aaaa);

    fasm(vc, cccc);

    int a[5], *p;

    printf("Input five numbers:");

    for (p = a; p < a + 5; p++)

    {

        scanf_s("%d", p);

    }

    for (p = a; p < a + 5; p++)

    {

        printf("M", *p);

    }

    printf("\n");

    //system("pause");

    SAMPLE s = { 'a',2,'b' };

    printf("byte=%d\n", sizeof(s));//计算结构体所占内存字节数

    sum = add(2, 3);

    printf("sum=%d\n", sum);

    getchar();

    return 0;

}

C语言调用的汇编语言的源程序为:

.586

.MODEL flat, STDCALL

.STACK 4096

option casemap : none;  大小写敏感

includelib "E:\vs2015\project\msvcrt.lib"

; 为外部提供的东东在这儿声明

public C vasm, aaaa, fasm

; 外部的变量在这儿声明

extern C vc : ptr BYTE, cccc : DWORD

; 函数不论内外都在这儿声明

printf  PROTO C : ptr byte, : vararg

fc      PROTO C : ptr BYTE, : DWORD

fasm    PROTO C : ptr BYTE, : DWORD

chr$ MACRO any_text : vararg

LOCAL textname

.const

textname db any_text, 0

ALIGN 4

.code

EXITM <OFFSET textname>

ENDM

.data

vasm DWORD offset casm; !!!!!为外部提供的数组要这么声明!!!!!

casm BYTE  "汇编的变量欢迎你!"; !!!casm作为共享变量传出去是其内容,而不是地址!!!

aaaa    DWORD 0aaaah

.code

fasm proc C uses ebx, s:ptr BYTE, d : DWORD

invoke printf, chr$(0dh, 0ah, "====现在执行的是汇编语言程序====", 0dh, 0ah)

invoke printf, chr$("    显示外部C变量的内容:  %s  X ", 0dh, 0ah), vc, cccc

invoke printf, chr$("    显示参数的内容:  %s  X ", 0dh, 0ah), s, d

invoke printf, chr$("    !汇编中开始调用C的函数!", 0dh, 0ah)

invoke fc, s, d

invoke printf, chr$("====汇编语言程序现在执行完毕====", 0dh, 0ah, 0dh, 0ah)

ret

fasm endp

END

11.对于程序

int main(int argc,char *argv[])
    {
for (int i = 0; i < argc; i++)
{
printf("第个参数为:%s\n",i,argv[i]);
}
getchar();
return argc++;
  } 

windows下的可执行程序是PE结构,PE结构按照操作系统的加载规范填充,由操作系统的加载器调入内存,分配进程ID执行,跟main函数无关。main函数只是在编译时,由编译器识别,编译成可执行文件,main函数跟可执行程序无关,知识个标识

argc是命令行总的参数个数  
   argv[]是argc个参数,其中第0个参数是程序的全名,以后的参数  
   命令行后面跟的用户输入的参数

在 C89 中,main( ) 是可以接受的。Brian W. Kernighan 和 Dennis M. Ritchie 的经典巨著 The C
programming Language 2e(《C 程序设计语言第二版》)用的就是 main( )。不过在最新的 C99 标准中,只有以下两种定义方式是正确的: 
           int main( void ) 
           int main( int argc, char *argv[] ) 

当然,我们也可以做一点小小的改动。例如:char *argv[] 可以写成 char **argv;argv 和 argc 可以改成别的变量名(如 intval 和 charval),不过一定要符合变量的命名规则。 

如果不需要从命令行中获取参数,请用int main(void) ;否则请用int main( int argc, char *argv[] )。 

main 函数的返回值类型必须是 int ,这样返回值才能传递给程序的激活者(如操作系统)。 

如果 main 函数的最后没有写 return 语句的话,C99 规定编译器要自动在生成的目标文件中(如 exe 文件)加入return 0; ,表示程序正常退出。不过,最好在main函数的最后加上return 语句。

三、比较C与汇编的优缺点,适应场合

C语言属于高级语言,具有可移植性,能够结构化编程。使用标准C语言的程序,几乎都可以不作改变移植到不同的微机平台上,对于嵌入式等的微控制芯片,属于标准C语言的部分也很少需要修改,而且程序很容易读懂。
C语言编写程序结构清晰,移植性好,容易维护和修改。

C语言对操作系统和系统使用程序以及需要对硬件进行操作的场合,用C语言明显优于其它高级语言,许多大型应用软件都是用C语言编写的。
C语言具有绘图能力强,可移植性,并具备很强的数据处理能力,因此适于编写系统软件,三维,二维图形和动画它是数值计计算的高级语言。

汇编语言针对不同的操作系统平台,不同的微控制器,指令都是完全不同的,即使指令相似,也不具有可移植性。但是汇编语言是针对专门的控制器的,所以运行速度可以精确到一个指令周期。汇编语言的程序读懂需要借助微控制器的指令手册以及各个寄存器的说明,所以很难读懂。
汇编语言编写代码实时性强,能够直接控制硬件的工作状态,但是不具有可移植性,维护和修改困难。

汇编的应用主要是单片机和微机程序,还有一些计算机外部设备的驱动程序,主要是一些要求程序运行效率的场合,以及时间要求精确的场合,主要都是用汇编。

四、总结与分析,顿悟与畅想

这次的汇编大作业是一个挺大的项目了。在完成的过程中,我通过查找课本,PPT,网上的资料加上跟同学讨论最终算是基本上完成了题目的要求。不过还是有一些要求没有完成。我感觉在这个过程当中查找资料的能力很重要,一个人的知识毕竟是有限的,而只有能够熟练和快速地通过不同的渠道找到自己需要的资料,这对我以后的学习和研究也是影响很大的。另外,通过这次的作业,我发现自己对汇编语言和C语言基本知识的掌握还存在一些问题,而有些问题在平常中是很难遇到的,这为我在接下来的几天复习汇编的时间里指明了重点和方向。通过对汇编语言和C语言的比较能更加清晰地掌握和理解这两门语言。这也启示我在以后的学习中要更多地通过联系和比较的方法来学习新的知识。
五、本课程你的其他收获及希望