【新书推荐】2.2 变量

本节必须掌握的知识点:

        示例三源代码

   代码分析

   汇编解析

变量可以分为全局变量、局部变量、static静态变量和STL线程存储变量。在函数内定义的变量,叫做局部变量。在函数外定义的变量,叫做全局变量。本节先简单介绍局部变量的使用方法。我们将在第九章函数中详细讲解局部变量、全局变量和static静态变量。STL线程存储变量我们将在《Windows API每日一练》一书中详细讲解。

2.2.1 示例三

变量和声明

示例三声明两个int类型的变量a和b。int为英文单词integer的缩写,为有符号数整型变量,可以是负整数,也可以是正整数。

鼠标选中VS左侧解决方案资源管理器“源文件”,点击鼠标右键,选择“添加”>“新建项(W)…”,新建项目2-2-1.c。编辑源代码如下所示:

示例代码3

/*

   为两个变量赋整数值并显示

*/

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

    int a = 1;//声明int类型变量a,并赋初始值1。

    int b = 2;//声明int类型变量b,并赋初始值2。

    printf("a=%d\tb=%d\n", a, b);//将变量a和b以十进制整数输出;\t是制表符

    a = b;//将b的值赋给a,此时a的值发生了变化。

    printf("a=%d\tb=%d\n", a, b);//打印的a是发生变化后的值, b并没有变化。

    b = a + b;//将b+a的值赋给b,此时b的值发生了变化。

    a = b + a;//将b+a的值赋给a,此时a的值发生了变化。

    printf("a=%d\tb=%d\n", a, b);//此时打印的是发生变化后变量a和b的值。

    system("pause"); //在程序执行return 0; 之前暂停

    return 0;

}    

 注意

1.要使用变量必须通过声明明确其类型和名称。

       2.变量的使用必须遵循3个原则:一是有确定的地址;二是有已分配的存储空间;三是已完成初始化。

       3.在实际工程中,变量的命令要有实际意义,而不是示例代码中的字母符号。

       4.system函数是C语言工具库中的函数,在头文件stdlib.h中声明。参数"pause"为系统命令行参数,功能为暂停批处理文件的处理并显示消息。如果直接运行编译后的exe程序,控制台窗口会一闪而过,无法看到输出的结果。添加system函数调用可以在程序结束前暂停,方便观察控制台窗口输出的结果。

       5.C语言表达式中的运算符前后添加空格,可以增强代码的可读性。

       6.VS中int类型为32位有符号整数,取值范围是-231~231-1。但在其他编译器中,int类型有可能是16位有符号整型。在定义int类型变量时需要注意其取值范围。

    ●输出结果:

    a=1     b=2

a=2     b=2

a=6     b=4

2.2.2 代码分析

变量的定义

变量声明格式:

变量类型 变量名 = 初始值(或不赋初始值)。

示例三中的定义了两个变量,其中变量a赋初始值为1,变量b没有赋初始值。

例:

int  a = 1;     //赋初识值,已初始化

其真实的含义为:在偏移地址a处,以int类型为单位,分配4个字节空间,并存入初始值1对照变量三原则,变量a可以直接在代码中使用。

int  b;    //未初始化

其真实含义为:在偏移地址b处,以int类型为单位,分配4个字节空间,未存入初始值。对照变量三原则,变量b未初始化前不可以在代码中直接使用。

上述语句也可以换一种写法:

int a,b;    //定义int类型变量a和b

a= 1;       //将变量a赋初始值1

变量的命名规则

在C语言中,变量的命名有明确规则:

只能由字母、数字、下划线组成;

第一个字符必须是英文字母;

有效长度为255个字符;

不可以包含标点符号和类型说明符(%、&、!、#、@、$);  

不可以是关键词。

●关键词:

由ANSI标准定义的C语言关键字共32个:auto、 double、 int、 struct、 break 、else、 long、 switch、case、 enum、 register 、typedef、 char 、extern 、return、 union 、const、 float 、short、 unsigned、 continue、 for、 signed 、void、default 、goto、 sizeof 、volatile、 do 、if 、while、 static。

上述32个关键字已经被C语言本身使用,不能作为其他用途使用,比如不能定义成变量名、函数名。

●常用的命名规则

1.驼峰命名法:

特点:多个单词组合,除第一个单词外每个单词的首字母大写(也称小驼峰)。

示例:iPadMini,mciSendSrting。

2.帕斯卡命名法:

特点:每一个单词的首字母大写,其余小写(也称大驼峰)。

示例:FirstName,OuGuang。

3.匈牙利命名法:

开头字母用变量类型的缩写,其余部分用变量的英文或者英文缩写,单词首字母大写。

示例:iMyAge,cMyName,fManHeight。

4.全大写:通常用于常量宏定义

示例:#define MAXSIZE 10。

5._t :一般是别名

示例:size_t,time_t。

举例

正确的变量命名:

int nName = 11;

int i_Age = 18;

错误的变量命名:

int 1Name = 0;       不能以数字开头!只能是字母、数字、下划线组成。

int case = 12;          不能以关键字作为变量名!只能是字母、数字、下划线组成。

int %age = 13;         不能用标点符号!只能是字母、数字、下划线组成。

int name age = 12;       不能用空格!只能是字母、数字、下划线组成。

具体命名形式请查看【附录C--代码规范】目前阶段只需要了解。

变量的初始化

实验十:变量未初始化

●第一步:在VS中将示例三的变量声明修改为:

int a ;//未初始化

int b ;//未初始化

●第二步:按F7编译源代码,输出窗口显示信息如下:

1>d:\code\asm_to_c\myprojectone\chapter2\2-2\2-2-1.c(15): error C4700: 使用了未初始化的局部变量“b”

1>d:\code\asm_to_c\myprojectone\chapter2\2-2\2-2-1.c(15): error C4700: 使用了未初始化的局部变量“a”

实验十一:变量初始化

●第一步:在源代码“int a = 1;”处按F9下断点。

●第二步:按F5调试执行,查看反汇编窗口,反汇编代码如下:

    int a = 1;//声明int类型变量a,并赋初始值1。

00BA4348  mov         dword ptr [a],1 

    int b = 2;//声明int类型变量b,并赋初始值2。

00BA434F  mov         dword ptr [b],2 

printf("a=%d\tb=%d\n", a, b);//将变量a和b以十进制整数打印出来;\t是制表符

●第三步:在监视1窗口内名称栏输入“&a”和“&b”,如图2-2所示:

图2-2 监视窗口获取局部变量a和b的地址和存储值

打开监视窗口方法:断下之后,点击VS菜单“调试”>“窗口”>“监视”。符号‘&’为地址符,“&a”意思是取变量a的地址,如图2-2所示,变量a的地址为0x0095fbfc,该地址处存储的值为{0xcccccccc}。同样,变量b的地址为0x0095fbf0,该地址处存储的值也是{0xcccccccc}。

【注意】:程序每次运行,系统分配的变量地址是不同的。

●第四步:按F10单步执行,如图2-3所示:

图2-3 F10单步执行

int a = 1;

对应的汇编语句为:

mov dword ptr [a] , 1

接下来将要执行的是语句:int b = 2;

此时观察监视1窗口,如图2-4所示:

图2-4 变量a赋值后的值

如图2-4所示,执行int a = 1;语句后,变量a地址处存储的值为0x00000001,变量b的初始值未变。

 结论

1. 变量在未初始化的情况下,不可以在代码中直接使用。

2. 在函数内定义的变量称为局部变量,又称为自动变量。局部变量定义在函数的堆栈空间,未初始化之前的值为0xcc或者不确定。

不确定值:因为计算机存储介质并不是空白的,相反存放了很多以前运行的无用数据,当我们生成变量时,系统会分配内存空间,而分配的内存空间是之前遗留下来无用的数据的空间,有可能未被初始化为0。由于我们并没有给变量赋值,所以系统就随机分配无用数据,就造成了变量会被存入一个不确定的值,我们可以看作垃圾值。

0xcc:因为在main函数内声明的变量a和b是局部变量,其内存空间在堆栈内分配,在main函数初始化时,编译器自动给堆栈空间内分配了0D8H个字节的局部变量空间,并且将其全部初始化为0xcc(36h个0CCCCCCCCh)。下面是示例三的反汇编代码:

00BA4320  push        ebp 

00BA4321  mov         ebp,esp      ;建立堆栈框架

00BA4323  sub         esp,0D8h     ;分配0D8h个字节局部变量空间

00BA4329  push        ebx         ;保护寄存器

00BA432A  push        esi          ;保护寄存器

00BA432B  push        edi          ;保护寄存器

00BA432C  lea         edi,[ebp-0D8h];初始化局部变量堆栈空间 

00BA4332  mov         ecx,36h          ;重复次数36H(0D8H/4=36H)

00BA4337  mov         eax,0CCCCCCCCh  ;初始化值,4个字节cch

00BA433C  rep stos    dword ptr es:[edi];重复36H次存储局部变量堆栈空间0CCCCCCCCh

3.变量在使用前必须符合变量三原则,有确定的地址,确定的存储空间、确定的初始值。

4.C语言编译器与汇编器有所不同,C语言编译器会检查变量的是否已初始化。而汇编语言中全局变量的初始值默认为0,局部变量是否填充0xcc取决于编译器,汇编器编译时不做检查。

5.除了局部变量之外,还有全局变量和static静态变量、TLS存储变量。我们将在后面的章节中详细介绍,此处不再赘述。

变量赋值

示例代码三中,语句int a = 1;中的等号“=”表示把右边的常量值1赋给左边的变量a,可以通过“=”来改变变量的值。

如果用汇编语句来写:

mov sdword ptr a,1      ; sdword ptr表示32位有符号整数类型

准确的含义是指:将常量值1存入内存偏移地址a处的四个字节空间,并且存入的常量值可以是-231~231-1之间的任一整数值。

【注意】这里的等号和数学中的“x=1”含义是不同的。

变量输出

    示例三通过调用printf函数输出变量a和b的值:

printf("a=%d\tb=%d\n", a, b);//将变量a和b以十进制整数打印出来;\t是制表符

    printf语句包含3个参数。第一个参数为格式化常量字符串,字符串中包含两个格式化说明符’%d’,分别对应第二个参数变量a和第三个参数变量b,表示按照有符号整数的格式分别输出变量a和变量b的值。

    此外,格式化常量字符串中还包含两个转义字符,’\t’表示输出制表符,’\n’表示输出换行。 

2.2.3 汇编解析

    ■汇编代码

    ;FileName:2-2-1.asm

;例3:示例代码2-1为两个变量赋整数值并显示

;by:bcdaren

;2023.08.27

;===============================

;C标准库头文件和导入库

include vcIO.inc

.data  

a   sdword 1    ;全局变量

b   sdword 2    ;全局变量

szMsg db "a=%d",09h,"b=%d",0dh,0ah,0    ;制表符ASCII码为09h

.code

start:

    push b

    push a

    push offset szMsg   ;格式化常量字符串偏移地址入栈

    call printf         ;调用printf函数输出结果

    ;a = b;

    mov eax,b

    mov a,eax  

    invoke printf,offset szMsg,a,b;控制台窗口输出变量a和变量b的值

    ;b = a + b;

    mov eax,b

    add eax,a

    mov b,eax

    ;a = b + a;

    mov eax,b

    add eax,a

    mov a,eax

    invoke printf,offset szMsg,a,b

    ;  

    invoke _getch   ;等待输入单个字符

    ret             ;结束返回

end start

    上述代码为2-2-1.c的汇编代码实现。vcIO.inc头文件在示例一中已经详细讲述,此处不再赘述。

.data数据段定义了格式化常量字符串szMsg。此外还定义了两个sdword类型的全局变量a和b。

 注意

汇编语言变量的定义和C语言有区别:

1.汇编语言中定义的变量a和b是全局变量,数据类型为sdword类型,而C语言中定义的变量a和b是局部变量,数据类型是int类型。汇编器标准数据类型sdword等价于VS C\C++编译器标准数据类型int,都是表示32位有符号整数类型。汇编语言中,main函数内使用的变量通常定义为全局变量,但是全局变量有风险,其他函数调用时有可能无意中会改变全局变量的值,需要程序员时刻保持警惕。而C语言处于安全角度考虑,在变量定义时,就限定了局部变量的使用范围,仅在函数内有效,相对而言,对程序员的要求不再那么严格。

【注】如果汇编代码使用main函数,也可以将变量a和b定义为局部变量。后面的章节我们会实现。

2.C语言的赋值语句非常简单。

例如:a = b;

翻译为汇编语句:

mov eax,b

mov a,eax

需要借助于eax累加器完成变量间的赋值,这是由CPU指令规则决定的。

3.C语言的算术运算可以直接使用运算符实现。

例如:b = a + b;

翻译成汇编语句:

mov eax,b

add eax,a      ;add加法指令

mov b,eax

同样需要借助于累加器eax实现。

4.C语言直接调用库函数printf输出变量a和b的值,函数名后的圆括号内为实参,以分号结束语句。实参按照从右往左的顺序入栈。

printf("a=%d\tb=%d\n", a, b);

翻译成汇编语句:

push b

push a

push offset szMsg ;格式化常量字符串偏移地址入栈

call printf              ;调用printf函数输出结果

5.本书采用ANSI字符集C语言标准库函数,因此字符串中的字符均为ASCII字符。本例格式化字符串定义:

szMsg db "a=%d",09h,"b=%d",0dh,0ah,0     ;制表符ASCII码为09h

转义字符直接改为ASCII码字符,数据类型为db类型,相当于C语言中的char类型。转移字符’\t’的ASCII码值为09h,转义字符’\n’在Windows操作系统中对应的ASCII码字符为0dh,0ah,使用两个字节表示。而在Unix或Linux操作系统中,换行符为0ah。

    ■反汇编代码

        int a = 1;//声明int类型变量a,并赋初始值1。

01274348  mov         dword ptr [a],1 

    int b = 2;//声明int类型变量b,并赋初始值2。

0127434F  mov         dword ptr [b],2 

    printf("a=%d\tb=%d\n", a, b);//将变量a和b以十进制整数打印出来;\t是制表符

01274356  mov         eax,dword ptr [b] 

01274359  push        eax 

0127435A  mov         ecx,dword ptr [a] 

0127435D  push        ecx 

0127435E  push        offset string "a=%d\tb=%d\n" (01277B30h) 

01274363  call        _printf (0127104Bh) 

01274368  add         esp,0Ch 

    a = b;//将b的值赋给a,此时a的值发生了变化。

0127436B  mov         eax,dword ptr [b] 

0127436E  mov         dword ptr [a],eax 

printf("a=%d\tb=%d\n", a, b);//此时打印的a,是发生变化后的值,而b并没有发生变化。

01274371  mov         eax,dword ptr [b] 

01274374  push        eax 

01274375  mov         ecx,dword ptr [a] 

01274378  push        ecx 

01274379  push        offset string "a=%d\tb=%d\n" (01277B30h) 

0127437E  call        _printf (0127104Bh) 

01274383  add         esp,0Ch 

    b = a + b;//将b+a的值赋给b,此时b的值发生了变化。

01274386  mov         eax,dword ptr [a] 

01274389  add         eax,dword ptr [b] 

0127438C  mov         dword ptr [b],eax 

    a = b + a;//将b+a的值赋给a,此时a的值发生了变化。

0127438F  mov         eax,dword ptr [b] 

01274392  add         eax,dword ptr [a] 

01274395  mov         dword ptr [a],eax 

    printf("a=%d\tb=%d\n", a, b);//此时打印的是发生变化后变量a的值、变量b的值。

01274398  mov         eax,dword ptr [b] 

0127439B  push        eax 

0127439C  mov         ecx,dword ptr [a] 

0127439F  push        ecx 

012743A0  push        offset string "a=%d\tb=%d\n" (01277B30h) 

012743A5  call        _printf (0127104Bh) 

    printf("a=%d\tb=%d\n", a, b);//此时打印的是发生变化后变量a的值、变量b的值。

012743AA  add         esp,0Ch 

    system("pause"); //在程序执行return 0; 之前暂停

012743AD  mov         esi,esp 

012743AF  push        offset string "pause" (01277B40h) 

012743B4  call        dword ptr [__imp__system (0127B174h)]

012743BA  add         esp,4

    为了节约篇幅,上述反汇编代码没有包含堆栈框架。对比汇编代码,反汇编代码中没有使用高级汇编伪指令invoke,而是单条汇编指令组成,每条反汇编代码只有一个汇编指令。每条汇编指令对应一条机器指令,这才是计算机真实的执行过程。

    我们可以把程序理解为:程序员编写的一段计算机控制指令,控制计算机各个部件的运行,已完成算术逻辑运算,实现数据的移动。

    当我们深刻理解C语言语句执行的过程,才可以真正理解所编写的C语言代码的确切含义。当C语言程序运行出现错误时,也可以通过反汇编代码,逐条指令单步执行,以发现错误的具体原因。

    C语言的语法非常灵活,为了便于掌握C语言各种不同语法的实现,下面我们做几个变量声明的实验。

实验十二:变量和声明

VS新建项目2-2-2.c:

    /*

        为两个变量赋整数值并显示

*/

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

    int x, y;    //声明int型变量x,y

    x = 11;      //x变量初始化赋值

    y = x + 10;

    printf("x的值是%d。\n", x);     //显示x的值

    printf("y的值是%d。\n", y);     //显示y的值

    system("pause"); //在程序执行return 0; 之前暂停

    return 0;

}

●输出结果:

x的值是11。

y的值是21。

请按任意键继续. . .

练习

  1. 请读者将2-2-2.c翻译成汇编语言实现。
  2. 请读者分析2-2-2.c的反汇编代码。

实验十三:变量初始化

VS新建项目2-2-3.c:

/*

   为两个变量赋整数值并显示

*/

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

    int x = 11;      //声明x变量,并初始化赋值

    int y = 10;      //声明y变量,并初始化赋值

    printf("x的值是%d。\n", x);     //显示x的值

    printf("y的值是%d。\n", y);     //显示y的值

    system("pause"); //在程序执行return 0; 之前暂停

    return 0;

}

●输出结果:

x的值是11。

y的值是10。

请按任意键继续. . .

练习

  1. 请读者将2-2-3.c翻译成汇编语言实现。
  2. 请读者分析2-2-3.c的反汇编代码。

实验十四:变量声明时初始化

VS新建项目2-2-4.c:

/*

   为两个变量赋实数值并显示

   只显示整数部分,小数部分丢失

*/

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

    int x = 3.14;

    int y = 5.7;

    printf("x的值是%d。\n", x);     //显示x的值

    printf("y的值是%d。\n", y);     //显示y的值

    system("pause"); //在程序执行return 0; 之前暂停

    return 0;

}

●输出结果:

x的值是3。

y的值是5。

请按任意键继续. . .

练习

  1. 请读者将2-2-4.c翻译成汇编语言实现。
  2. 请读者分析2-2-4.c的反汇编代码。

本文摘自编程达人系列教材《汇编的角度——C语言》。

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值