在我们学习当中,我们会思考很多问题,下面我们就提出几个针对前面学习的东西的一些疑惑。
下面函数运行结果的原因
#include<stdio.h>
int main(){
int b = -5;
unsigned int a = 10;
if(a < b){
printf("a < b\n");
}else{
printf("a > b \n")
}
return 0;
}
这段代码的运行结果居然会是a<b。这是为什么呢?
原因在于,在c语言中,当有符号整型和无符号整型进行比较时候,系统会把有符号整型做一个类型提升,提升为无符号整型。我们都知道-5在计算机的存储方式是以补码存在的。一旦最高为的1不再表示符号位,而是数值为,那么远远会比10大得多,这也就是这道题目的原因。
凡是思考下面这样一段话是否正确
凡是函数内部定义的都在stack区,都是全局变量。凡是函数外部定义的变量都在data区,是全局变量?
这段话是有问题的,因为当我们定义一个stact的静态局部变量时候,这个时候就不是在stack区,而是data区。
字面常量放于内存四区中哪个位置
数据区(静态区)
全局变量和静态变量存放在此. 里面细分有一个常量区, 字符串常量和其他常量也存放在此. 该区域是在程序结束后由操作系统释放.
分析continue,break,return,goto的区别
continue
continue的作用是跳出本次的循环,并且要进行下一次的循环。他在不一样的循环中,跳出的位置也不一样,比如在for循环中,当我们执行了continue以后,那么循环体后面的代码将不再执行,而是跳转到for(xxx1;xxx2;xxx3)中的xxx3语句开始下一次的循环。但是当在while语句或者do{}while(xxx1);当中,continue会跳转到xxx1语句继续判断执行。continue一般只在循环结构中出现,而且一般在一个选择结构中。
break
break语句不只是可以在循环中使用,并且可以在switch语句中使用。在循环中的break意味着结束整个循环周期,跳出离他最近的一层的循环,实际上break也可以用于多层循环的跳出,这样一段代码:
#include<stdio.h>
int main(){
go: for(int i = 0; i < 10; ++i){
for(int j = 0; j < 10; ++j){
if(3 == i){
break go;
}
}
}
return 0;
}
给外层循环起个名字就可以跳出了。
break在switch语句中的作用是:使得程序跳出switch,执行switch结构以后的代码。
return
c语言的return也就意味着当前函数停止执行。
return 语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个 return 语句被执行,所以只有一个返回值。
函数一旦遇到 return 语句就立即返回,后面的所有语句都不会被执行到了。从这个角度看,return 语句还有强制结束函数执行的作用。
return 语句是提前结束函数的唯一办法。return 后面可以跟一份数据,表示将这份数据返回到函数外面;return 后面也可以不跟任何数据,表示什么也不返回,仅仅用来结束函数。
goto
goto 语句是一种无条件流程跳转语句,通常 goto 语句与 if 语句结合使用,当满足一定条件时,程序流程跳转到指定标号处,接着往下执行。
其中,“语句标识”可以是任一个合法的标识符,如 pos_1、pos_2、label_1、label_2 等都是合法的语句标识。注意,语句标识后的冒号不能省略。
程序将从对应“语句标号”的代码处开始往下执行。
e.g
#include<stdio.h>
int main (void){
int n;
pos_1://字母数字下划线组成
printf("请输入一个正整数:");
scanf("%d",&n);
if(n<0)
{
printf("输入错误!\n");
goto pos_1;
}
printf("成功输入正整数:%d\n",n);
return 0;
}
通过上述执行流程及运行结果的分析,可以发现该例中使用 goto 跳转语句实现了循环的功能。故可以使用循环结构的代码替换该 goto 结构的代码。使用 goto 语句可能会造成程序层次不清晰,可读性差,故在实际编程中,应尽量少使用或避免使用 goto 语句。
如何终止宏定义
#undef 标识符
用来将前面定义的宏标识符取消定义。
这样一段实例代码:
#include<stdio.h>
#define int int *
int main(){
int a;
#ifdef int
#undef int
#endif
int b;
}
这样就可以实现局部替换了,后面如果想要使用可以继续#define来定义。
了解一部分汇编指令
JMP 无条件转移指令
ADD 加法.
MOV 传送字或字节.
PUSH,POP 把操作数压入或取出堆栈
SUB 减法指令
EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器。
EBX 是"基地址"(base)寄存器, 在内存寻址时存放基地址。
ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
EDX 则总是被用来放整数除法产生的余数。
ESI/EDI 分别叫做"源/目标索引寄存器"(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串.
EBP 是"基址指针"(BASE POINTER), 它最经常被用作高级语言函数调用的"框架指针"(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码:
push ebp ; 保存当前ebp
mov ebp,esp ; EBP设为当前堆栈指针
sub esp, xxx ; 预留xxx字节给函数临时变量.
…
这样一来,EBP 构成了该函数的一个框架, 在EBP上方分别是原来的EBP, 返回地址和参数. EBP下方则是临时变量. 函数返回时作 mov esp,ebp/pop ebp/ret 即可.
ESP 专门用作堆栈指针,被形象地称为栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP也就越来越小。在32位平台上,ESP每次减少4字节。
EIP 寄存器,用来存储CPU要读取指令的地址,CPU通过EIP寄存器读取即将要执行的指令。每次CPU执行完相应的汇编指令之后,EIP寄存器的值就会增加。
RET 栈顶字单元出栈,其值赋给IP寄存器。即实现了一个程序的转移,将栈顶字单元保存的偏移地址作为下一条指令的偏移地址。
CALL call指令经常和ret指令配合使用,因为CPU执行call指令进行两部操作将当前IP或CS和IP压入栈中转移(相当于jmp指令)call指令不能实现短转移(短转移8位位移,近转移16位位移),除此之外call指令实现转移的方法和jmp指令的原理相同(只是多了一个将CS、IP入栈)。
dword ptr:dword 双字 就是四个字节ptr pointer缩写 即指针
[]里的数据是一个地址值,这个地址指向一个双字型数据
比如mov eax, dword ptr [12345678] 把内存地址12345678中的双字型(32位)数据赋给eax 。
c语言为什么要设计const变量,const全局变量为什么必须初始化,已经c语言和c++语言中const的区别
const 最好是赋初值,否则可能出现不可预料的后果。
你无法排除编译器直接将const变量当成常量来用,另外,全局变量有些编译器会默认赋值0.
const关键字的作用主要有以下几点:
(1)可以定义const常量,具有不可变性。
例如:const int Max=100; int Array[Max];
(2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。
例如: void f(const int i) { …} 编译器就会知道i是一个常量,不允许修改;
(3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。
(4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。还是上面的例子,如果在函数体内修改了i,编译器就会报错;
例如:void f(const int i) { i=10;//error! }
(5) 为函数重载提供了一个参考。class A { …void f(int i) {…} //一个函数void f(int i) const {…} //上一个函数的重载 …};
(6) 可以节省空间,避免不必要的内存分配。
例如:#define PI 3.14159 //常量宏const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 …doublei=Pi; //此时为Pi分配内存,以后不再分配!double I=PI; //编译期间进行宏替换,分配内存double j=Pi;//没有内存分配double J=PI; //再进行宏替换,又一次分配内存!const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
(7) 提高了效率。编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
c和c++中区别与联系:
const int n = 10;
在c中n是一个只读变量,n的值运行时才知道,占有内存
在c++中n是常量,编译时就有值,直接替换使用的地方,不占内存
比如
const int n = 10;
int a[n];
用c编译是编译不过的,因为n是变量,编译的时候大小不知道
但用c++编译可以编译通过,因为n是常量,相当与 #defind n 3