C语言面试知识点-part1

第一部分:基本概念及其它问答题

1. static

static定义的变量与全局变量都会被放在全局区里,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后有系统释放。(关于内存分配,还有栈区(stack),堆区(heap),文字常量区,程序代码区)

虽然静态变量和全局变量都会存储在全局区,但作用域是有区别的,在此加以区分。

  • 全局变量具有全局作用域。你可以在一个文件中定义一个全局变量,然后在多个文件中访问这个变量。(需要使用extern关键字声明变量。注意“声明“与“定义”的区别。)
  • 静态局部变量具有局部作用域,一般是在函数体内声明及定义,所以作用域局限在函数体内,函数体外不可见。与局部变量的区别在于静态变量只被定义一次。(请不要被“在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。”所迷惑)。事实上受影响的只是通过static int a = 3;这种方式定义的静态局部变量。这条static int a = 3;不会被多次执行。所以一旦a值已经被改变了(你可以随便修改变量值,没有在调用过中维持值不变一说),下次再调用这个函数的时候,a不会被重新初始化为3,而是上一次执行时被保存的值。这主要是由于变量存在于全局区的原因。你想想,全局变量不也是会保存之前的值吗?要不然叫什么全局变量啊。但如果你static int a; a = 3; 那只有static int a 这一行不被执行,a还是会被改回3的。
  • 静态全局变量因为是在函数体外,所以在文件内的函数都是可见的。但在文件外是不可见的。也就是说如果你在一个文件里定义了静态变量,在其它的文件中是不可以访问的。说白了,static和extern是互斥的。static全局变量限定了变量只在文件内起作用,extern则是表明变量作用域可以扩展到其它文件。(没有修饰描述符,默认为extern)
  • 静态全局函数与静态全局变量的道理是一样的。

2. "引用"与"指针"的区别

https://www.cnblogs.com/gxcdream/p/4805612.html

  • C和C++使用指针,并且暴露给程序员,Java和C#也使用指针,但把指针隐藏起来了。
  • 引用和指针是有本质区别的,引用是别名,而指针是地址。因为引用只是对象的别名,所以定义引用的时候,就必须绑定对象,否则就没有意义了。同时引用一旦绑定了对象,就不能再修改到绑定其它对象了。而指针则不用,指针是指向某一地址的变量,是可以修改的。
  • 从内存分配上,指针是一个变量,所以需要分配内存的,而引用只是一个别名,只是指向绑定的对象,所以不需要再分配内存。(标准没有规定引用要不要占用内存,也没有规定引用具体要怎么实现,具体随编译器 http://bbs.csdn.net/topics/320095541)
  • 从编译上看,程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改,也就是说虽然符号表不能改,但对于指针变量来说,符号表只是存的指针变量的地址,但这个地址上的值是可以改变的,这样也就改变了指针指向的对象。但引用不用,引用在符号表里就是对象的地址,而这个地址又不能改,所以引用的对象是无法修改的),而引用对象不能改。这是使用指针不安全而使用引用安全的主要原因。从某种意义上来说引用可以被认为是不能改变的指针。
  • 引用的大小是所指向的变量的大小,因为引用只是一个别名而已;指针是指针(地址)本身的大小,32位系统下,一般为4个字节

3. 预处理指令(Preprocessor)

源代码被编译之前,由预处理器(Preprocessor)对源代码进行的处理。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的符号用来支持宏调用
预处理器的主要作用就是把通过预处理的内建功能对一个资源进行等价替换,最常见的预处理有:文件包含,条件编译、布局控制和宏替换4种。
#include (<>用来引用标准库头文件,""常用来引用自定义的头文件。<>编译器只搜索包含标准库头文件的默认 目录,“”首先搜索正在编译的源文件所在的 目录,找不到时再搜索包含标准库头文件的默认 目录.)
#if/#elif/#else/#endif
#ifdef
#define/#undef (在编译前,define的常量会直接替换源代码中的符号。#define的缺点:1. 不支持类型检查;2. 不考虑作用域;3. 符号名不能限制在一个命名 空间中;) 
定义宏
#define Print(Var) count<<(Var)<<endl
用宏名中的参数带入语句中的参数, 宏后面没有;号, Print(Var)中的Print和(之间不能有空格,否则(就会被解释为置换字符串的一部分; 所有的情况下都可以使用内联函数来代替宏,这样可以增强类型的检查。 注:给替换变量加引号)
#ifndef (防止重复引入某些头文件)
#line (重新定义当前行号或文件名)
#error(输出编译错误消息,停止编译)

标准的预处理器宏
LINE 当前源文件中的代码行号,十进制整数
FILE  源文件的名称,字符串字面量
DATE  源文件的处理日期,字符串字面量,格式mmm dd yyyy其中mmm是月份如Jan、Feb等 dd是01-31 yyyy是四位的年份
TIME 源文件的编译 时间,也是字符串字面量格式是hh:mm:ss
STDC 这取决于实现方式,如果编译器选项设置为编译标准的C代码,通常就定义它,否则就不定义它
__cplusplus 在编译C++ 程序时,它就定义为199711L

#pragma (设定编译器的状态或者是指示编译器完成一些特定的动作)
#pragma message("") 当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。
#pragma code_seg([“section-name”[,“section-class”]]) 它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它
#pragma once 只要在头文件的最开始加入这条指令就能够保证头文件被编译一次
#pragma hdrstop 表示预编译头文件到此为止,后面的头文件不进行预编译 (预编译头文件可以加快链接速度,但又会占用磁盘空间,所以使用这个选项排除一些头文件)
  #pragma startup指定编译优先级
  #pragma package(smart_init) ,BCB就会根据优先级的大小先后编译
  #pragma resource ".dfm"表示把.dfm文件中的资源加入工程
  #pragma warning(disable:450734;once:4385;error:164)  //不显示4507和34号警告信息 //4385号警告信息仅报告一次  //把164号警告信息作为一个错误
  #pragma warning(push)保存所有警告信息的现有的警告状态。
  #pragma warning(push,n)保存所有警告信息的现有的警告状态,并且把全局警告等级设定为n
  #pragma warning(pop)向栈中弹出最后一个警告信息
  #program comment(comment-type,[“commentstring”])将一个注释记录放入一个对象文件或可执行文件。
  #program data_seg用来建立一个新的数据段并定义共享数据 (A. #pragma data_seg()一般用于DLL中。也就是说,在DLL中定义一个共享的有名字的数据段。最关键的是:这个数据段中的全局变量可以被多个进程共享,否则多个进程之间无法共享DLL中的全局变量。B.共享数据必须初始化,否则微软编译器会把没有初始化的数据放到.BSS段中,从而导致多个进程之间的共享行为失败)
#program region用于折叠特定的代码段
 #pragma disable 在函数前声明,只对一个函数有效。该函数调用过程中将不可被中断。一般在C51中使用较多
#program pack(n) 规定按n字节对齐

4. 对齐

1.什么是对齐?为什么要对齐?
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。

2.pragma pack语法
用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题,有时候为了内存对齐需要补齐空字节。通常写程序的时候,不需要考虑对齐问题。编译器会替我们选择适合目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。
语法:#pragma pack( [show] | [push | pop] [, identifier], n )
show(optical):显示当前packing aligment的字节数,以warning message的形式显示。(可选参数;显示当前packing aligment的字节数,以warning message的形式被显示)
push(optical): (可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈;)
Pushes the current packing alignment value on the internal compiler stack, and sets the current packing alignment value to n. If n is not specified, the current packing alignment value is pushed.
pop(optical): (可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略;)
Removes the record from the top of the internal compiler stack. If n is not specified with pop, then the packing value associated with the resulting record on the top of the stack is the new packing alignment value. If n is specified, for example, #pragma pack(pop, 16), n becomes the new packing alignment value. If you pop with identifier, for example, #pragma pack(pop, r1), then all records on the stack are popped until the record that hasidentifier is found. That record is popped and the packing value associated with the resulting record on the top of is the stack the new packing alignment value. If you pop with an identifier that is not found in any record on the stack, then the pop is ignored.
identifier(optional): (可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作)
When used with push, assigns a name to the record on the internal compiler stack. When used with pop, pops records off the internal stack until identifieris removed; if identifier is not found on the internal stack, nothing is popped.
n (optional): (可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16)
Specifies the value, in bytes, to be used for packing. If the compiler option /Zp is not set for the module, the default value for n is 8. Valid values are 1, 2, 4, 8, and 16. The alignment of a member will be on a boundary that is either a multiple of n or a multiple of the size of the member, whichever is smaller.
3.结构体对齐规则
结构体中各个成员按照它们被声明的顺序在内存中顺序存储。

1)将结构体内所有数据成员的长度值相加,记为sum_a;
2)将各数据成员内存对齐,按各自对齐模数而填充的字节数累加到和sum_a上,记为sum_b。对齐模数是【该数据成员所占内存】与【#pragma pack指定的数值】中的较小者。
3)将和sum_b向结构体模数对齐,该模数是【#pragma pack指定的数值】、【未指定#pragma pack时,系统默认的对齐模数8字节】和【结构体内部最大的基本数据类型成员】长度中数值较小者。结构体的长度应该是该模数的整数倍。

3.1 基本数据类型所占内存大小
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
3.4关于类
空类是会占用内存空间的,而且大小是1,原因是C++要求每个实例在内存中都有独一无二的地址。
(一)类内部的成员变量:
• 普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。
• static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。
(二)类内部的成员函数:
• 普通函数:不占用内存。
• 虚函数:要占用4个字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的
#pragma pack(4)
class cBase{};
sizeof(cBase)=1
在这里插入图片描述
3.4.2 子类
子类所占内存大小是父类+自身成员变量的值。特别注意的是,子类与父类共享同一个虚函数指针,因此当子类新声明一个虚函数时,不必在对其保存虚函数表指针入口。
在这里插入图片描述

5. 局部变量、 全局变量、 堆、 堆栈、 静态和全局

一个由C/C++编译的程序占用的内存分为以下几个部分
(1)栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。
(2)堆区(heap) — 由程序员分配和释放,若程序员不释放,程序结束时可能由OS回收。
(3)全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量
和静态变量在一块区域, 未初始化的全局变量、未初始化的静态变量在相邻的另一块区域。
(4)文字常量区 — 常量字符串就是放在这里的。
(5)程序代码区 — 存放函数体的二进制代码。
堆栈(stack)是内存中的一个连续的块。一个叫堆栈指针的寄存器(SP)指向堆栈的栈顶。堆栈的底部是一个固定地址。堆栈有一个特点就是,后进先出。也就是说,后放入的数据第一个取出。
堆(heap)是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。

1. 在高级语言中,程序函数调用、函数中定义的变量都用到栈(stack)
2. 用malloc, calloc, realloc等函数分配得到变空间是在堆(heap)上
3. 在所有函数体外定义的是全局量
4. 加了static修饰符后不管放在在哪里都属于静态变量,存放在全局区(静态区)
5. 在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用
6. 在函数体内定义的static表示只在该函数体内有效
7. 函数中的"armfly"这样的字符串存放在常量区
所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。 如果加了static,就会对其它源文件隐藏。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围
C++中的类成员声明static(有些地方与以上作用重叠)
静态成员函数不含this指针。
静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。
类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致 了它仅能访问类的静态数据和静态成员函数。
局部变量:局部变量也只有局部作用域,它是自动对象(auto),它在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回。
全局变量:全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern关键字再次声明这个全局变量。
静态局部变量:静态局部变量具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。
静态全局变量:静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。
堆亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。
栈在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
静态存储区内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量

6. 堆(heap)和栈(stack)的区别

  1. 申请方式不同
    Stack: 由系统自动分配
    Heap: 需要程序员自己申请,并指明大小。如:malloc(10).
  2. 申请后系统的响应
    stack: 只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出
    heap:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
    会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中
  3. 申请大小的限制
    stack: 在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小
    heap: 堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大
  4. 申请效率的比较
    stack: 由系统自动分配,速度较快。但程序员是无法控制的
    heap: 是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便
    Note: 另外,在WINDOWS下,最好的方式是用Virtual Alloc分配内存,他不是在堆,也不是在栈,而是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活
  5. 堆和栈中的存储内容
    stack: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
    当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行
    heap: 一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排
  6. 存取效率的比较

7. 关键字const

此处只介绍几个基本的性质。

  1. const声明变量不能被修改,即为只读
  2. const变量必须初始化
  3. const分为两种,一种是顶层const,指针本身是一个常量(即地址不允许改变,如int* const a. 实际上int a 同样也是顶层const, a的地址同样不能改变。),另一种是底层const,指针所指的对象是一个常量(指针本身是可以修改的, 只是指向的值不能进行修改).
  4. #define 和const 定义的常量在定义是均不会分配内存,在使用时,第一次使用const常量使分配内存,以后不再分配。而使用define常量时,每使用一次,分配一次内存
  5. const 修饰类数据成员,成员函数,类对象,形参
    a. 在成员函数形参后添加const,表明该函数不会修改类的数据成员(int* function() const)
class A
{
public:
    int& getValue() const
    {
        // a = 10;    // 错误
        return a;
    }

private:
    int a;            // 非const成员变量
};

b. const修饰数据成员表示该数据成员一旦赋值就不可变,并且只能在类的构造函数初始化列表中赋值,不能在类构造函数体内赋值

class A
{
public:
    A(int x) : a(x)  // 正确
    {
         //a = x;    // 错误
    }
private:
    const int a;
};

c. const修饰类对象,该对象内的任何成员变量都不能被修改,因此不能调用该对象的任何非const成员函数,因为对非const成员函数的调用会有修改成员变量的企图

class A
{
 public:
    void funcA() {}
    void funcB() const {}
};

int main
{
    const A a;
    a.funcB();    // 可以
    a.funcA();    // 错误

    const A* b = new A();
    b->funcB();    // 可以
    b->funcA();    // 错误
}

d. const修饰形参,指不可改变形参。(function(const int &x),这里x是一个常量引用,如果没有const,那么在函数内对x的修改会反应到实参上,但由于有const,则表明x不能被修改。但与直接传值是不同的,传值的话会有两次copy,但通过这种方式只有一次copy)
6. const的常量值是可以被强制修改的。(const int i=0; int p = (int)&i; *p=100。通过另外一个指向同一地址的变量修改

下面介绍几个例子:
a. const int a 和int const a是一样的,a不能修改。
b. const int *b 和 int const *b是一样的,与放在int 左右没有关系,b不能修改。即值不能修改
c. int * const c,c不能修改。也就是指针不能修改,只能指向一个地址。
d. const int * const d, 则是
d和d都不能修改
详细介绍请参考:
https://blog.csdn.net/Function_Dou/article/details/84987468

8. 关键字volatile

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面列举几个应用场景。
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量

9. 结构与联合

(1). 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)
(2). 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的

10. 内存分配方式

1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量
2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集
3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多

11. const与#define

Const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被Const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性
1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误
2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试

12. 宏与函数的优缺点

函数
处理时间 编译时 程序运行时
参数类型 没有参数类型问题 定义实参、形参类型
处理过程 不分配内存 分配内存
程序长度 变长 不变
运行速度 不占运行时间 调用和返回占用时间

13. 中断(Interrupts)

  1. 中断的响应过程
    首先,断点处的PC压栈(当前执行的指令位置),做断点保护(硬件自动执行)。然后有关的寄存器内容和标志位状态压栈(哪些寄存器由程序员决定),做现场保护(用户编程完成)。然后就可以跳转到中断服务程序执行,执行完毕,中断返回。中断返回需要做现场恢复,与上面的现场保护做逆操作,保存的寄存器内容和标志位弹栈,由用户编程实现。然后通过RETI恢复PC值,跳回原中断点继续执行。
  2. 中断优先级
    由于同一时间可能会有多个中断请求,出现中断的竞争。根据中断的紧迫程序,需要定义优先权级别,优先级高的会被优先执行。计算机按中断源优先权高低逐次响应的过程称优先权排队。
  3. 中断嵌套
    当CPU正在响应一个中断时,有更高优先权的中断请求,CPU就会暂停当前的中断,转去执行更高优先权的中断,这就被称为中断嵌套。也就是说一般情况下,中断是可以被再次中断的。
  4. 中断处理机制
    计算机内存的前1024个字节(偏移量00000H到003FFH)保存着256个中断向量,每个中断向量占4个字节,前两个字节保存着中断服务程序的入 口地址偏移量,后两个字节保存着中断程序的入口段地址,使用时,只要将它们分别调入寄存器IP及CS中,就可以转入中断服务程序实现中断调用。每当中断发 生时,CPU将中断号乘以4,在中断向量表中得到该中断向量地址,进而获得IP及CS值,从而转到中断服务程序的入口地址,调用中断。这就是中断服务程序通过中断号调用的基本过程。
    在计算机启动的时候,BIOS将基本的中断填入中断向量表,当DOS得到系统控制权后,它又要将一些中断向量填入表中,还要修 改一部分BIOS的中断向量。有一部分中断向量是系统为用户保留的,如60H到67H号中断,用户可以将自己的中断服务程序写入这些中断向量中。不仅如此,用户还可以自己更改和完善系统已有的中断向量。
  5. C语言中断示例
    在C语言中,提供了一种函数类型interrupt,专门用来定义中断服务程序。
    void interrupt int60() { puts(“This is an example”); }
    但如何把函数入口地址填写到中断向量表中呢?可以通过setvect()和getvect()函数。setvect的功能是将指定的函数安装到指定的中断向量中,getvect返回值是该中断的入口地址。在安装中断以前,最好用disable函数关闭中断,以防止在安装过程中又产生新的中断而导致程序运行混乱,待安装完成后,再用enable 函数开放中断,使程序正常运行。

猜你喜欢

转载自blog.csdn.net/gzzhy/article/details/91882895