ATPCS即ARM-THUMBprocedure call standard的简称。
PCS规定了应用程序的函数可以如何分开地写,分开地编译,最后将它们连接在一起,所以它实际上定义了一套有关过程(函数)调用者与被调用者之间的协议。 PCS强制实现如下约定:调用函数如何传递参数(即压栈方法,以何种方式存放参数),被调用函数如何获取参数,以何种方式传递函数返回值。 PCS的制订是一系列指标的“tradeoff(折衷)(”因为很大程度上涉及系统的一些性 能),如会涉及生成代码的大小,调试功能的支持,函数调用上下文处理速度以及内存消耗。当然,通过编译器的支持可以让生成的代码有不同的特性,如gcc编 译选项可以支持或不支持framepointer来支持深入调试功能或提高程序运行性能。 PCS是体系结构密切相关的,直接涉及编译器如何使用处理器提供的应用寄存器,如编译器使用什么寄存器作为栈指针,利用哪些寄存器作直接传参等。值得注意的是,没有谁规定说PCS是必须这样而不是那样的。它是应用相关的。任何一个操作系统和应用可以处于它自身的考虑定义自己的PCS。当然,如果那样,也必须有自己的编译器。而实际上,在一个处理器设计时,都会有某种假设,所以PCS某种程度上应该 是一样的。 ATPCS就是基于ARM指令集和THUMB指令集过程调用的规范。
ATPCS概述 为了使单独编译的C语言程序和汇编程序之间能够相互调用,必须为子程序之间的调用规定一定的规则.ATPCS就是ARM程序和THUMB程序中子程序调用的基本规则...
一.ATPCS概述...
ATPCS规定了一些子程序之间调用的基本规则.这些基本规则包括子程序调用过程中寄存器的使用规则,数据栈的使用规则,参数的传递规则.为适应一些特定的需要,对这些基本的调用规则进行一些修改得到几种不同的子程序调用规则,这些特定的调用规则包括:支持数据栈检查的ATPCS. 支持只读段位置无关的ATPCS. 支持可读写段位置无关的ATPCS. 支持ARM程序和THUMB程序混合使用的ATPCS.处理浮点运算的ATPCS...
有调用关系的所有子程序必须遵守同一种ATPCS. 编译器或者汇编器在ELF格式的目标文件中设置相应的属性,标识用户选定的ATPCS类型.对应不同类型的ATPCS规则,有相应的C语言库,连接器根据用户指定的ATPCS类型连接相应的C语言库...
使用ADS的C语言编译器编译的C语言子程序满足用户指定的ATPCS类型.而对于汇编语言程序来说,完全要依赖用户来保证各子程序满足选定的ATPCS类型. 具体来说,汇编语言子程序必须满足下面三个条件: 在子程序编写时必须遵守相应的ATPCS规则; 数据栈的使用要遵守ATPCS规则; 在汇编编译器中使用-apcs选项... 二. 基本ATPCS...
基本ATPCS规定了在子程序调用时的一些基本规则,包括以下三个方面的内容: 各寄存器的使用规则及其相应的名字; 数据栈的使用规则; 参数传递的规则. 相对于其他类型的ATPCS,满足基本ATPCS的程序的执行速度更快,所占用的内存更少. 但是它不能提供以下的支持: ARM程序和THUMB程序相互调用; 数据以及代码的位置无关的支持; 子程序的可重入性; 数据栈检查的支持.而派生的其他几种特定的ATPCS就是在基本ATPCS的基础上再添加其他的规则而形成的.其目的就是提供上述的功能... 寄存器的使用规则:
1. 子程序通过寄存器R0~R3来传递参数. 这时寄存器可以记作: A1~A4 , 被调用的子程序在返回前无需恢复寄存器R0~R3的内容.
2. 在子程序中,使用R4~R11来保存局部变量.这时寄存器R4~R11可以记作: V1~V8 .如果在子程序中使用到V1~V8的某些寄存器,子程序进入时必须保存这些寄存器的值,在返回前必须恢复这些寄存器的值,对于子程序中没有用到的寄存器则不必执行这些操作.在THUMB程序中,通常只能使用寄存器R4~R7来保存局部变量.
3.寄存器R12用作子程序间scratch寄存器,记作ip; 在子程序的连接代码段中经常会有这种使用规则.
4. 寄存器R13用作数据栈指针,记做SP,在子程序中寄存器R13不能用做其他用途. 寄存器SP在进入子程序时的值和退出子程序时的值必须相等.
5. 寄存器R14用作连接寄存器,记作lr ; 它用于保存子程序的返回地址,如果在子程序中保存了返回地址,则R14可用作其它的用途.
6. 寄存器R15是程序计数器,记作PC ; 它不能用作其他用途. 7. ATPCS中的各寄存器在ARM编译器和汇编器中都是预定义的. 参数的传递规则.
根据参数个数是否固定,可以将子程序分为参数个数固定的子程序和参数个数可变的子程序.这两种子程序的参数传递规则是不同的. 1.参数个数可变的子程序参数传递规则
对于参数个数可变的子程序,当参数不超过4个时,可以使用寄存器R0~R3来进行参数传递,当参数超过4个时,还可以使用数据栈来传递参数. 在参数传递时,将所有参数看做是存放在连续的内存单元中的字数据。然后,依次将各名字数据传送到寄存器R0,R1,R2,R3; 如果参数多于4个,将剩余的字数据传送到数据栈中,入栈的顺序与参数顺序相反,即最后一个字数据先入栈. 按照上面的规则,一个浮点数参数可以通过寄存器传递,也可以通过数据栈传递,也可能一半通过寄存器传递,另一半通过数据栈传递. 2.参数个数固定的子程序参数传递规则
对于参数个数固定的子程序,参数传递与参数个数可变的子程序参数传递规则不同,如果系统包含浮 点运算的硬件部件,浮点参数将按照下面的规则传递: 各个浮点参数按顺序处理;为每个浮点参数分配FP寄存器;分配的方法是,满足该浮点参数需要的且编号最小的一组连续的FP寄存器.第一个整数参数通过寄存 器R0~R3来传递,其他参数通过数据栈传递. 子程序结果返回规则
1.结果为一个32位的整数时,可以通过寄存器R0返回.
2.结果为一个位整数时,可以通过R0和R1返回,依此类推.
3.结果为一个浮点数时,可以通过浮点运算部件的寄存器f0,d0或者s0来返回. 4.结果为一个复合的浮点数时,可以通过寄存器f0-fN或者d0~dN来返回. 5.对于位数更多的结果,需要通过调用内存来传递.
ARM函数调用时参数传递规则
时间:2009-11-08 21:56来源:PCB设计网 作者:网络 点击: 352次
之前在学习如何在C语言中嵌入汇编时有了解到C语言之前的参数调用是使用寄存器R0传递第一个参数,R1传递到第二个..一直到R3传递第四个参数.但是 实际上有时可能传递的参数非常多,超过8个,或是参数中有浮点数之类,参数也会超过4个寄存器,对于超出的部份并不使用
之前在学习如何在C语言中嵌入汇编时有了解到C语言之前的参数调用是使用寄存器R0传递第一个参数,R1传递到第二个..一直到R3传递第四个参数.但是 实际上有时可能传递的参数非常多,超过8个,或是参数中有浮点数之类,
参数也会超过4个寄存器,对于超出的部份并不使用R4,而是使用堆栈的方式,但具体 是如何的方式很多网站就没了下文了,好在在GG的帮助下,让我在凌晨1.30找到了(为啥老是在半夜呢?)
对于ARM体系来说,不同语言撰写的函数之间相互调用(mix calls)遵循的是 ATPCS(ARM-Thumb Procedure Call Standard),ATPCS主要是定义了函数呼叫时参数的传递规则以及如何从函数返回,关于ATPCS的详细内容可以查看ADS1.2 Online Books ——Developer Guide的2.1节。这篇文档要讲的是 汇编代码中对C函数调用时如何进行参数的传递以及如何从C函数正确返回
不同于x86的参数传递规则,ATPCS建议函数的形参不超过4个,如果形参个数少于或等于4,则形参由R0,R1,R2,R3四个寄存器进行传递;若形参个数大于4,大于4的部分必须通过堆栈进行传递。
我们先讨论一下形参个数为4的情况. 实例1:
test_asm_args.asm
//——————————————————————————– IMPORT test_c_args ;声明test_c_args函数 AREA TEST_ASM, CODE, READONLY EXPORT test_asm_args test_asm_args
STR lr, [sp, #-4]! ;保存当前lr
ldr r0,=0×10 ;参数 1 ldr r1,=0×20 ;参数 2 ldr r2,=0×30 ;参数 3 ldr r3,=0×40 ;参数 4 bl test_c_args ;调用C函数
LDR pc, [sp], #4 ;将lr装进pc(返回main函数) END test_c_args.c
//——————————————————————————– void test_c_args(int a,int b,int c,int d) {
printk(”test_c_args:\\n”);
printk(”%0x %0x %0x %0x\\n”,a,b,c,d); } main.c
//——————————————————————————– int main() {
test_asm_args(); for(;;);
}
程序从main函数开始执行,main调用了test_asm_args,test_asm_args调用了test_c_args,最后从test_asm_args返回main.
代码分别使用了汇编和C定义了两个函数,test_asm_args 和
test_c_args,test_asm_args调用了test_c_args,其参数的传递方式就是向R0~R3分别写入参数值,之后使用bl语句 对test_c_args进行调用。其中值得注意的地方是用红色标记的语句,test_asm_args在调用test_c_args之前必须把当前的 lr入栈,调用完test_c_args之后再把刚才保存在栈中的lr写回pc,这样才能返回到main函数中。
如果test_c_args的参数是8个呢?这种情况test_asm_args应该怎样传递参数呢? 实例2:
test_asm_args.asm
//——————————————————————————– IMPORT test_c_args ;声明test_c_args函数 AREA TEST_ASM, CODE, READONLY EXPORT test_asm_args test_asm_args
STR lr, [sp, #-4]! ;保存当前lr
ldr r0,=0×1 ;参数 1 ldr r1,=0×2 ;参数 2 ldr r2,=0×3 ;参数 3 ldr r3,=0×4 ;参数 4 ldr r4,=0×8
str r4,[sp,#-4]! ;参数 8 入栈 ldr r4,=0×7
str r4,[sp,#-4]! ;参数 7 入栈 ldr r4,=0×6
str r4,[sp,#-4]! ;参数 6 入栈 ldr r4,=0×5
str r4,[sp,#-4]! ;参数 5 入栈 bl test_c_args_lots
ADD sp, sp, #4 ;清除栈中参数 5,本语句执行完后sp指向 参数6 ADD sp, sp, #4 ;清除栈中参数 6,本语句执行完后sp指向 参数7 ADD sp, sp, #4 ;清除栈中参数 7,本语句执行完后sp指向 参数8 ADD sp, sp, #4 ;清除栈中参数 8,本语句执行完后sp指向 lr LDR pc, [sp],#4 ;将lr装进pc(返回main函数) END test_c_args.c
//——————————————————————————–
void test_c_args(int a,int b,int c,int d,int e,int f,int g,int h) {
printk(”test_c_args_lots:\\n”);
printk(”%0x %0x %0x %0x %0x %0x %0x %0x\\n”, a,b,c,d,e,f,g,h); } main.c
//——————————————————————————– int main() {
test_asm_args(); for(;;); }
这部分的代码和实例1的代码大部分是相同的,不同的地方是test_c_args的参数个数和test_asm_args的参数传递方式。
在test_asm_args中,参数1~参数4还是通过R0~R3进行传递,而参数5~参数8则是通过把其压入堆栈的方式进行传递,不过要注意这四个入栈参数的入栈顺序,是以参数8->参数7->参数6->参数5的顺序入栈的。 直到调用test_c_args之前,堆栈内容如下: sp->+———-+
| 参数5 | +———-+ | 参数6 | +———-+ | 参数7 | +———-+ | 参数8 | +———-+
| lr | +———-+
test_c_args执行返回后,则设置sp,对之前入栈的参数进行清除,最后将lr装入pc返回main函数,在执行 LDR pc, [sp],#4 指令之前堆栈内容如下: +———-+ | 参数5 | +———-+ | 参数6 | +———-+ | 参数7 | +———-+ | 参数8 | sp->+———-+
| lr | +———-+
但实际上可能不同的编译器可能用着不同的处理方式,于我们所使用的编译器我们可以写一个简单的代码,调用10个参数的函数,然后升成汇编再查看它是如何处理,这样再根据编译器进行特殊的优化.
1、读程序,回答问题
int main(int argc,char *argv[]) {
int c=9,d=0; c=c++%5; d=c;
printf(\"d=%d\\n\return 0; }
a)、写出程序的结果;
b)、在一个可移植的系统中这种表达式是否存在风险?why? 答:a)、4
b)、存在风险,因为c=c++%5;在这个表达式中,对C有两次修改,行末未定义,c的值不明确。 2、#include \"stdio.h\" int a=0; //data section int b; //data section static char c; //BSS
int main(int arg c,char *argv[]) {
char d=4; //stack static short e;//BSS a++; b=100;
c=(char)++a; e=(++d)++;
printf(\"a=%d, b=%d, c=%d, d= %d, e=%d\return 0; }
a) 写出程序输出
b) 编译器如果安排各个变量(a,b,c,d)在内存中的布局(eg. stack,heap,data section,bss section),最好用图形方式描述。 答: a=2 b=100 c=2 d=6 e=5 3 C/C++基础知识问题
a) 关键字volatile在编译时有什么含义?并给出三个不同使用场景的例子(可以伪代码或者文字描述)。
b) C语言中static关键字的具体作用有哪些 ? c) 请问下面三种变量声明有何区别?请给出具体含义
int const *p; int* const p;
int const* const p;
答:a)、用volatile关键字定义变量,相当于告诉编译器,这个变量的值会随时发生变化,每次使用的时候都需要去内存里面 重新读取他的值,并不要随意去针对他做优化。 建议使用volatile关键字的地方: 1、并行设备的硬件寄存器
2、一个中断服务子程序中会访问到的非自动变量 3、多线程应用中被几个任务共享的非自动变量
b) 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被在声明它的模块的本地范围内使用。 static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;
static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝 c) 一个指向常整型数的指针 一个指向整型数的常指针 一个指向常整型数的常指针
4 嵌入式系统相关问题
a) 对于整形变量A=0x12345678,请画出在little endian及big endian的方式下在内存中是如何存储的。
b) 在ARM系统中,函数调用的时候,参数是通过哪种方式传递的? c) 中断(interrupt,如键盘中断)与异常(exception,如除零异常)有何区别? a)
little: 高地址 0x12 0x34 0x56 0x78 低地址 big: 低地址 0x12 0x34 0x56 0x78 高地址
b)参数<=4时候,通过R0~R3传递,>4的通过压栈方式传递。具体参数传递见下一篇。 c) 异常:在产生时必须考虑与处理器的时钟同步,实践上,异常也称为同步中断。在处理器执行到由于编程失误而导致的错误指令时,或者在执行期间出现特殊情况(如缺页),必须靠内核处理的时候,处理器就会产生一个异常。
所谓中断应该是指外部硬件产生的一个电信号,从cpu的中断引脚进入,打断cpu当前的运行;
所谓异常,是指软件运行中发生了一些必须作出处理的事件,cpu自动产生一个陷入来打断当前运行,转入异常处理流程。
1、 如何在C中初始化一个字符数组。 2、 如何在C中为一个数组分配空间。 3、 如何初始化一个指针数组。
4、如何定义一个有10个元素的整数型指针数组。 5、 s[10]的另外一种表达方式是什么。 6、 GCC3.2.2版本中支持哪几种编程语言。 7、 要使用CHAR_BIT需要包含哪个头文件。 8、 对(-1.2345)取整是多少? 9、 如何让局部变量具有全局生命期。 10、C中的常量字符串应在何时定义? 11、如何在两个.c文件中引用对方的变量。 12、使用malloc之前需要做什么准备工作。 13、realloc函数在使用上要注意什么问题。 14、strtok函数在使用上要注意什么问题。 15、gets函数在使用上要注意什么问题。1
6、C语言的词法分析在长度规则方面采用的是什么策略? 17、a+++++b所表示的是什么意思?有什么问题? 18、如何定义Bool变量的TRUE和FALSE的值。
19、C语言的const的含义是什么。在定义常量时,为什么推荐使用const,而不是#define。 20、C语言的volatile的含义是什么。使用时会对编译器有什么暗示。
这部分是ANSI C的一些问题,题目的前提是必须都答对,看似很变态,但是细想一下,这些都是最基础的,虽然我们在使用他们的时候会犯这样那样的错误,但是最终目的是不犯错误,不是么,那么好,从最基础的开始。 1、 如何在C中初始化一个字符数组。
这个问题看似很简单,但是我们要将最简单的问题用最严谨的态度来对待。关键的地方:初始化、字符型、数组。最简单的方法是char array[];。这个问题看似解决了,但是在初始化上好像还欠缺点什么,个人认为:char array[5]={\"1\或者char
array[5]={\"12345\或者char array[2][10]={\"China\也许更符合“初始化”的意思。 2、 如何在C中为一个数组分配空间。
最简单的方法是:char array[5];意思是分配给数组array一个5个字节的空间。但是我们要知道在C中数组其实就是一个名字,其实质含义就是指针,比如char array[];是到底分配的多少空间?所以我们要将其分成为两种不同的形式给出答案:一种是栈的形式:char array[5];一种是堆的形式:char *array; array=(char *)malloc(5);//C++: array=new char[5];堆和栈的含义其实我也没弄太透彻,改天明白了再发一篇。我们要明白的是,第一种形式空间分配的大小可能会受操作系统的,比如windows会在2M;第二种形式成空间分配很灵活,想分配多少分配多少,只要RAM够大。 3、 如何初始化一个指针数组。
首先明确一个概念,就是指向数组的指针,和存放指针的数组。
指向数组的指针:char (*array)[5];含义是一个指向存放5个字符的数组的指针。 存放指针的数组:char *array[5];含义是一个数组中存放了5个指向字符型数据的指针。 按照题意,我理解为初始化一个存放指针的数组,char *array[2]={\"China\;其含义是初始化了一个有两个指向字符型数据的指针的数组,这两个指针分别指向字符串\"China\"和\"Beijing\"。
4、如何定义一个有10个元素的整数型指针数组。
既然只是定义而不是初始化,那就很简单且没有争议了:int *array[10];。 5、 s[10]的另外一种表达方式是什么。
前面说过了,数组和指针其实是数据存在形态的两种表现形式,如果说对于数组s[],我们知道*s=s[0],那么s[10]的另一种表达方式就是:*(s+10)。 6、 GCC3.2.2版本中支持哪几种编程语言。
这个问题实在变态,就像问你#error的作用是什么一样。不可否认,gcc是linux下一个亮点,是一个备受无数程序员推崇的编译器,其优点省略1000字,有兴趣可以自己查,我翻了翻书,书上曰:支持C,C++,Java,Obj-C,Ada,Fortran,Pascal,Modula-3等语言,这个“等”比较要命,不过我认为已经很全了,如果认为还是不全,干脆把ASM也加上算了,不过那已经不算是编译了。
7、 要使用CHAR_BIT需要包含哪个头文件。
如果结合上面的问题,答题的人估计会认为自己撞鬼了,这个问题实在是……搜索了一下,应该是limits.h。
8、 对(-1.2345)取整是多少?
其实不同的取整函数可能有不同的结果,不过这个数没有太大的争议,答案是-1。
9、 如何让局部变量具有全局生命期。
具体的生命期的概念我觉得我还要好好深入的学习一下,但是这个题目还算比较简单,即用static修饰就可以了,但是只是生命期延长,范围并没有扩大,除非把这个变量定义在函数体外的静态区,不过那样就变成全局变量了,仿佛不符合题目要求。 10、C中的常量字符串应在何时定义?
这个问题说实话不是很理解题干的意思,据我理解,有两种情况,一种是预处理阶段,用#define定义;还有就是使用const修饰词,不过const修饰的是一个变量,其含义是“只读”,称之为常量并不准确,但是确实可以用操作变量的方法当常量用。所以还是第一种比较靠谱。 11、如何在两个.c文件中引用对方的变量。
这个问题也问的挺含糊的,怎么说呢,最简单最直接的方法是为变量添加extern修饰词,当然,这个变量必须是全局变量。还有一种就是利用函数调用来进行变量的间接引用,比如这个C文件中的一个函数引用另外一个C中的函数,将变量通过实参的形式传递过去。不过题目既然说是引用,那么还是用第一个答案好了。 12、使用malloc之前需要做什么准备工作。
其实准备工作很多啊,比如你需要一台计算机之类的。玩笑话,我们首先要知道malloc的用途,简单的说就是动态的分配一段空间,返回这段空间的头指针。实际的准备工作可以这么分:需要这段空间的指针是否存在,若不存在,则定义一个指针用来被赋值,还要清楚要返回一个什么类型的指针,分配的空间是否合理;如果指针已经存在,那么在重新将新的空间头地址赋值给这个指针之前,要先判断指针是否为NULL,如果不是要free一下,否则原来的空间就会被浪费,或者出错,free之后就按照前一种情形考虑就可以了。
13、realloc函数在使用上要注意什么问题。这个函数我也才知道的,汗一个。据我的初步理解,这个函数的作用是重新分配空间大小,返回的头指针不变,只是改变空间大小。既然是改变,就有变大、变小和为什么改变的问题。变大,要注意不能大到内存溢出;变小,那变小的那部分空间会被征用,原有数据不再存在;为什么改变,如果是想重新挪作他用,还是先free了吧。
14、strtok函数在使用上要注意什么问题。
这个问题我不知道能不能回答全面,因为实在是用的很少。这个函数的作用是分割字符串,但是要分割的字符串不能是常量,这是要注意的。比如先定义一个字符串:char array[]=\"part1,part2\";,strtok的原形是char *strtok(char *string, char *delim);,我们将\作为分隔符,先用pt=strtok(array,\,得到的结果print出来就是\"part1\",那后面的呢,要写成pt=strtok(NULL,\,注意,要用NULL,如果被分割的字符串会被分成N段,那从第二次开始就一直要用NULL。总结起来,需要注意的是:被分割的字符串和分隔符都要使用变量;除第一次使用指向字符串的指针外,之后的都要使用NULL;注意使用这个函数的时候千万别把指针跟丢了,不然就全乱了。 15、gets函数在使用上要注意什么问题。
这是一个键盘输入函数,将输入字符串的头地址返回。说到要注意的问题,我还是先查了一下网上的一些情况,需要注意的就是gets以输入回车结束,这个地球人都知道,但是很多人不知道的是,当你输入完一个字符串后,这个字符串可能依然存在于这个标准输入流之中,当再次使用gets的时候,也许会把上次输入的东西读出来,所以应该在使用之后用fflush(stdin);处理一下,将输入流清空。最后也还是要注意溢出的问题。关于这个答案我比较含糊,不知道有没有高人高见?
16、C语言的词法分析在长度规则方面采用的是什么策略? 我无语……闻所未闻啊……还是搜索了一下,有一篇文章,地址是:
http://202.117.80.9/jp2005/20/kcwz/wlkc/wlkc/03/3_5_2.htm,是关于词法分析器的。其中提到了两点策略: (1) 按最长匹配原则确定被选的词型;(2) 如果一个字符串能为若干个词型匹配,则排列在最前面的词型被选中。不知道是不是题干的要求,还是其他什么。我乃一介草民,望达人指点迷津!
17、a+++++b所表示的是什么意思?
有什么问题?这个东西(称之为东西一点都不过分)其实并没有语法错误,按照C对运算符等级的划分,++的优先级大于+,那么这句话会被编译器看做:(a++)+(++b),这回明白了吧。有什么问题,语法上没有问题,有的是道德上的问题!作为一个优秀的程序员,我们要力求语句的合法性和可读性,如果写这句的人是在一个team里,那么他基本会被打的半死……最后讨论一下结果:假设a之前的值是3,b是4,那么运行完这个变态语句后,a的值是4,b是5,语句的结果是8。
18、如何定义Bool变量的TRUE和FALSE的值。
不知道这个题有什么陷阱,写到现在神经已经大了,一般来说先要把TURE和FALSE给定义了,使用#define就可以:#define TURE 1#define FALSE 0如果有一个变量需要定义成bool型的,举个例子:bool a=TURE;就可以了。 19、C语言的const的含义是什么。
在定义常量时,为什么推荐使用const,而不是#define。首先,这个题干抽了10题回答的一个大嘴巴。关于常量的概念看来我要好好看看书了……我说过了,const修饰词可以将一个变量修饰为“只读”,这个就能称为常量么?姑且认为可以。回到题目中,const是只读的意思,它限定一个变量不允许被改变,谁都不能改!既然是修饰变量,那么变量的类型就可以丰富多彩,int啊,char啊,只要C认识的都可以;但是#define就不可以了,在预处理阶段缺乏类型检测机制,有可能会出错。还有就是变量可以extern,但是#define就不可以。貌似const还可以节省RAM,这个我倒是没有考证过。至于const的用法和作用,有很多,我会总结后发上来。
20、C语言的volatile的含义是什么。使用时会对编译器有什么暗示。
终于最后一题了,容易么……如果这个测试是一个关于嵌入式的,那么这道题非常重要!!从词面上讲,volatile的意思是易变的,也就是说,在程序运行过程中,有一些变量可能会
被莫名其妙的改变,而优化器为了节约时间,有时候不会重读这个变量的真实值,而是去读在寄存器的备份,这样的话,这个变量的真实值反而被优化器给“优化”掉了,用时髦的词说就是被“和谐”了。如果使用了这个修饰词,就是通知编译器别犯懒,老老实实去重新读一遍!可能我说的太“通俗”了,那么我引用一下“大师”的标准解释:volatile的本意是“易变的” 。由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化,但有可能会读脏数据。当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。 下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难
好了,答完了,也不知道标准答案是什么。如果有达人看到皱眉头的话,千万停下来,浪费您宝贵的几分钟时间指点一二,不胜感激!
、\"匈牙利命名法\"有什么优缺点?(2分)
2、下面x, y, *p的值是多少,有什么问题?(2分) int x, y, z = 2; int *p=&z; x=sizeof*p;
y=x/*p; // x=?, *p=?, y=?, 有什么问题?
3、下面的语句是什么意思?如何声明或定义才使它们更易懂?(10分) int (*foo())(); int (*foo())[]; int (*foo[])(); (*(void(*)())0)();
void (*signal(int,void(*)(int)))(int);
4、本题(2分)。一般使用malloc时,需要进行强制类型转换,如: char *s; s = (char *)malloc(31);
下面中???该如何填写,才可以正确执行强制类型转换?
int (*monthp)[31]; monthp = (???)malloc(31);
5、关于C语言运算符优先级的记忆技巧是什么?(2分) // 下面r的值是多少 int hi, low, r; hi=7;low=3; r=hi<<4+low;
6、指针和数组的区别是什么?用一个简单的声明把它们区分开。(2分)
指针和数组的声明在什么情况下是相同的?(2分)
7、C语言的左值(lvalue)和右值(rvalue)的含义是什么?(2分)
8、为什么C语言可以实现printf(char *format, ...)这样可变参数的调用形式?这样有什么缺点?(2分)
9、说明C语言中术语\"声明\"\"定义\"\"原型\"的含义?(2分)
10、举一个例子,说明使用assert和防错代码的区别。(5分)
11、对语句 if else 与操作符 ? : 使用场合的比较。(2分)
12、编写一个函数,输入一个的整型数字,可以选择按照8/10/16进制输出字符串。 注意边界值。(5分)
13、本题(2分)。下面是一个16x16的黑白图标: static unsigned short stopwatch[] = { 0x07c6, 0x1ff7, 0x383b, 0x600c, 0x600c, 0xc006, 0xc006, 0xdf06, 0xc106, 0xc106, 0x610c, 0x610c, 0x3838, 0x1ff0, 0x07c0, 0x0000, };
如何修改声明,可以使之在源代码中形象地表现出图形的模样。
14、说出可以使用calendar[11][30]变量的四种类型定义。(5分)
如:int calendar[12][31]; // 二维数组
15、使用strcmp,当字符串相同时会返回'\\0'。但'\\0'一般作为逻辑假,
因此下面的语句不容易理解:
if (!strcmp(s, \"string\")) return EQUATION;
如何经过简单修改,使之更易懂?(2分)
16、编写一个自己的完全C语言版本的memset函数,并且评价这个实现的性能和可移植性。(5分)
17、在树和图这些数据结构中,通常使用指针来组织数据。如果我们要把这些数据保存到文件中,指针是没有意义的。我们该如何解决这个问题。(2分)
18、用2种不同的方法计算long变量的\"1\"bit的个数。(2分)
19、任意给出一个C的基本数据类型,如何编码判断这个数据类型是有符号还是无符号的?(2分)
不得上机实验,写出下面代码的输出。解释这个行为是标准定义的,还是依赖实现的。(2分)
int i;
for (i = 0; i < 10; i++) { int j = i;
printf (\"%d\\n\}
20、列出5种以上你所看过的C编程的书籍,并写简要书评。(5分)
对C的评价。如果要你改造一把菜刀,使之更加安全,你是否会使用这样的菜刀,为什么?(5分)
-----华丽的分割线-----
1、\"匈牙利命名法\"有什么优缺点?(2分)
首先,我们要知道有这么个东西,因为这可能是目前公认的程序员的良好素质。匈牙利命名法是一种编程时的命名规范。基本原则是:变量名=属性+类型+对象描述,其中每一对象的名称都要求有明确含义,可以取对象名字全称或名字的一部分。匈牙利命名法是一位叫 Charles Simonyi 的匈牙利程序员发明的,后来他在微软呆了几年,于是这种命名法就通过微软的各种产品和文档资料向世界传播开了。
然后,我们来讨论一下优点。虽然我很少涉足OOP,但是对这个东西多少知道一点。匈牙利命名法非常便于记忆,而且使变量名非常清晰易懂,这样,增强了代码的可读性,方便各程序员之间相互交流代码。而且由于是从微软出来的东西,现在又变成了世界范围内的编码规范,所以使在熟悉这一命名法的程序员之间交流代码变得轻松。我们一直强调要有良好的程序风格,而这就需要从命名开始。
最后,我们再来讨论一下缺点。其实很少人说名压力命名法的缺点,所以我认为这道题的目的并不是让你骨头里挑刺,而是要看你有没有“怀疑一切,否定一切”的态度,是要看你是不是喜欢逆来顺受,要看你是不是能对一件看似既定的事情说“NO!”。那么好,给你一个机会,但也不能太离谱。凡是都有两面性,在匈牙利命名法给我们带来清晰的命名规范的同时,我们不得不承认,它的命名成本是很高的。比如一个变量,可能在程序里会出现100次,在debug阶段的时候忽然觉得它应该是另一个类型,那么好,噩梦开始,你要修改100遍啊100遍!还
有匈牙利命名法的收益,有一些简单的不能再简单,清楚的不能再清楚的变量,非要加上一串帽子,即便是一个简单的函数和变量,我们是不是也要停下来仔细琢磨一下它想传递一个什么值呢?在类型越来越多,越来越复杂的情况下,匈牙利命名法可能会画蛇添足,火上浇油,比如一个名为ppp的帽子,想说明什么?是Point to Point Protocol么?显然不是,它的意思是指向指针的指针的指针,晕吧。
好了,大致是这样,每个人都有心中的不满,导致这个题目没有标准答案,就像我说的,这道题的目的在于你是不是能够说出“不”,而不是走大众路线。引用AMD创始人Jerry Sanders的一句名言:只有偏执狂才能生存。我们虽然不偏执,但是总要有自己的想法和愤怒,不是么。 给出一篇参考文献,值得仔细品味:http://baike.baidu.com/view/419474.htm
2、下面x, y, *p的值是多少,有什么问题?(2分) int x, y, z = 2; int *p=&z; x=sizeof*p;
y=x/*p; // x=?, *p=?, y=?, 有什么问题?
有什么问题……我想说的是,如果你按这个写出来,肯定编译不过去。为什么?最明显的:y=x/*p;,这是什么东西?为p;做个注释?不至于懒得加一个括号吧,y=x/(*p);。
类似的问题,x=sizeof(*p);是不是更好看,这是编码素质的问题。好了,解决了这个问题,可以编译了,但是我们需要一个前提:你的宿主机是多少位的?下面我们用32bits举例,因为嵌入式linux都是跑在
32bits的ARM上的。一句一句来: int x, y, z = 2; //x=?,y=?,z=2
int *p=&z; //等价于int *p;p=&z; 所以p为指向z的数据的地址,*p=z=2
x=sizeof(*p); //考点来了,32bits
的CPU运行完这
句,x=4,同理,如果换成16bits的,x=2
y=x/(*p); //y=4/2=2
那么最后的结果就是:x=2,y=2,*p=2。很简单吧。
3、下面的语句是什么意思?如何声明或定义才使它们更易懂?(10分) int (*foo())(); int (*foo())[]; int (*foo[])(); (*(void(*)())0)();
void (*signal(int,void(*)(int)))(int); 第一个:int (*foo())();
先一步一步的分析:首先是被左边的()括起来的*foo()。 (1) 里面()的优先级高于*,所以foo()先是一个函数; (2) *foo()说明这个函数返回一个指针;
(3) (*foo())()这个函数返回的指针指向一个函数; (4) int (*foo())();
(5) 最后这个函数返回一个整型数。
好了,我们来一遍全的:这个函数的含义是foo()函数返回的指针指向一个函数,该函数返回一个整型数。(我汗啊……)
第二个:int (*foo())[];
有上面的打基础了,这回好多了。大致还是那个意思,但是就是由函数变成了数组,所以还更简单些,具体的含义是:foo()函数返回的指针指向一个具有整型数的数组。
第三个:int (*foo[])();
这个就是上两个的翻版:一个存有指针的数组*foo[],该指针指向一个函数,该函数返回一个整型数。
第四个:(*(void(*)())0)();
明显比上面的复杂,但是不要怕,还是那样,一层一层的分析: (1) 最里面的(*)()意思是一个指向函数的指针;
(2) 加个前缀void(*)(),表示一个指向返回类型为void的函数的指针; (3) 套个马甲(void(*)())的意思就是类型强制转换;
(4) (void(*)())0就是把0强制转换成前面说的那个类型的指针; (5) 那么好,现在(void(*)())0是一个指针了,这个指针指向了一个函数,我们用宏定义简化一下,看着太麻烦:
(6) #define ptr (void (*)())0 ,现在(*(void(*)())0)();这个怪物就可以简化成:(*ptr)();
(7) 别忘了ptr是一个指向函数的指针,既然这样*ptr就是函数了,那么上面的结果再次被简化成:ptr();,这不就是函数的调用么! 至此这个怪物函数的含义和简化形式就都给出了,我就不再总结了。不过我还想再给出一个简化形式,这会对下面的问题有启示:
使用typedef,typedef void (*ptr)(); ,然后就可以用(*(ptr)0)();来表示这个怪物函数了,其灵活性比使用#define要高。
第五个:void (*signal(int,void(*)(int)))(int); 这是著名的signal函数的原型,可以如此声明: typedef void (*HANDLER)(int); HANDLER signal(int,HANDLER); 或者:
typedef void HANDLER(int);
HANDLER *signal(int,HANDLER *);
随你喜欢,反正用起来没什么区别。这个函数简单的表述就是发出一个信号(int型),然后跳转到指向函数的指针HANDLER指向的信号处理函数处。
好了,都答完了,前三个我认为没有必要简化,所以只给出了后两个的简化。不知道做的对不对,不离十吧。
4、一般使用malloc时,需要进行强制类型转换,如: char *s; s = (char *)malloc(31);
下面中???该如何填写,才可以正确执行强制类型转换? (2分) int (*monthp)[31]; monthp = (???)malloc(31);
说实在的,我认为题干有一点点问题。我先来说一下我的思路:malloc的用法在上一部分说过了,不清楚的去翻书,或者翻我的文章。在这里int (*monthp)[31];是一个套,代表定义一个指向有31个整型数的数组的指针,我们如果吧这个数组看成一个连续的内存区域,那么
(*monthp)[31]原则上和*s没有什么区别,区别只是类型,如果只是强制类型转换,monthp = (int *)malloc(31); 就可以了。
但是这个题干本可以出的更精彩点,比如不给提示,直接int (*monthp)[31]; monthp = (???)malloc(???);,那么我们就要考虑到开辟空间的大小了,所以 monthp = (int *)malloc(sizeof(int)*31);应该是最完美的回答。
5、关于C语言运算符优先级的记忆技巧是什么?(2分) //下面r的值是多少 int hi, low, r; hi=7;low=3; r=hi<<4+low;
技巧……我不知道有什么技巧,摘一篇别人总结的:(1)先(括号)内层,后(括号)外层。(2)先函数,后运算。(3)先算术,后关系,再逻辑。(4)先乘除,后加减。(5)先左,后右。(6)搞不清,加括号。
我认为凡事都是一个熟能生巧的过程,技巧固然重要,但是以为追求技巧而忽略了原则,可谓得不偿失,早晚要栽跟头的。所以多用多练是最好的技巧。实在不行……就用括号!
回到题目中,位运算的级别是低于算术运算的,所以r=hi<<4+low;就变成了r=hi<<(4+low);,把数带进去就是r=7<<7;意思是把7左移7位,不用我再解释了吧,r=6。
6、指针和数组的区别是什么?用一个简单的声明把它们区分开。(2分) 指针和数组的声明在什么情况下是相同的?(2分)
这个问题可能在我们熟练使用指针和数组后已经很少去思考了。
第一个问题:
其实数组是一个地址,指针则是指向地址的地址。举个例子: char array[10]; char *pt; pt=array;
char array[10];的含义是,在内存里开辟一个10个字节的空间用来存放数据,其中array是这个空间的头地址,正如刚才所说,数组是一个地址。
char *pt;的含义是,只是定义一个指针pt,这个指针可以指向任意char型的地址,而指向的地址则存放在地址*pt中,也就是刚才说的指针是指向地址的地址。
那么好,pt=array;的意思就是我们把*pt中存放的地址(指针)指向了数组array[10]的头地址array,这个时候pt和array辩证的统一了,区别用通俗的话说就是数组是地名,指针是路牌。但是别忘了,在这个例子中,数组实实在在的占用了10个字节的空间,而指针只占用了4个字节用来存放地址而已(假设是32bits系统)。 最后我们总结一下区别:
数组:保存数据;直接访问数据;用于存储数目固定且类型相同的数据;由编译器自动分配和删除;自身即为数据名。
指针:保存地址;间接访问数据(先取得指针的内容,然后以它为地
址,取得数据);通常用于动态数据结构;动态的分配和删除;通常指向隐式数据。
第二个问题:
在第一个问题中,虽然指针和数组的本质定义永远不可能是一样的,但是在某种情况下也可以辩证的统一。 char array[10]={'0','1','2','3','4','5','6','7','8','9'}; char *pt=\"01234567\";
这样一来,它们在初始化阶段都占用了同样大小的内存空间,如此情况下,也就可以认为是相同的了。
7、C语言的左值(lvalue)和右值(rvalue)的含义是什么?(2分)
这个问题挺汗的,从来没仔细琢磨过其深层次的含义。阅览了一下文献,大致可以这么理解,当然也可能不对,我尽可能说的准确些: 左值就是一个可被存储的单元,右值就是一个可被读取的数据。 如此说笼统了一些,详细一些,就是左值必须是一个被明确了的内存存储单元,可以用来被赋值;右值必须是一个能被读出来的确确实实的值,这个值可以是数据,可以是指针,可以是结构,反正只要能被读出来的,都可以定义为右值。
大致我就理解这么多,可能不太准确,只是作为思考的参考,我也想知道最精准的答案。
8、为什么C语言可以实现printf(char *format, ...)这样可变参数的调用形式?这样有什么缺点?(2分)
关于这个问题,恐怕要上升到理论的高度,说实话,我并不清楚为什么,所以更谈不上缺点,经过阅览文献,我还是找出了一些蛛丝马迹,解释的比较笼统,希望有高人能用通俗的语言解答一下!
可变参数的函数,即函数的参数是不确定的。为了支持可变参数函数,C语言引入新的调用协议,即C语言调用约定 __cdecl。采用C语言编程的时候,默认使用这个调用约定。如果要采用其它调用约定,必须添加其它关键字声明。__cdecl 最大好处在于由于是调用者清理栈,它可以处理可变参数,缺点则在于它增加了程序的大小,因为在每个调用返回的时候,需要多执行一条清理栈的指令。
好了,我能理解的就这么多,希望真正回答这个问题的时候不要再被深入提及,否则就露馅了。在这个别和书签,有时间要好好琢磨一下。
9、说明C语言中术语\"声明\"\"定义\"\"原型\"的含义?(2分) 用函数举个例子,因为变量仿佛不存在原型的说法。
函数声明和函数原型其实很相似,函数声明是把函数的名字,函数类型以及形参的类型,个数和顺序通知编译系统,以便在调用该函数时系统进行对照检查,但是函数声明并不包括函数的功能。而函数的定义则是具体指出了函数要完成的功能。
函数的定义只能有一次,函数的原型只能有一个,而声明可以有多次,例如前面题目提到的著名的signal函数,原型是void (*signal(int,void(*)(int)))(int); ,但是可以声明成: typedef void (*HANDLER)(int); HANDLER signal(int,HANDLER);
当然也可以声明成其他的,声明后仍然只是个名字,而且已经和原型看似不同了,但是实际上将声明翻译回去,应该与原型不冲突。 我总结了一下,原型应该是函数最原始的形态,声明是将函数按照原型的形式声明成方便使用的形态,定义是为声明的函数添加具体的工作。 有可能不准确,但是我认为大致是这个意思。
10、举一个例子,说明使用assert和防错代码的区别。(5分) 我恐怕这5分得不到了,由此做出抛砖引玉吧。
说到防错代码,我第一联想到的是#ifdef、#ifndef、#else、#endif之类的在预处理阶段的一些宏和一些条件判断。而assert函数则是在程序中使用的宏(注意,其实assert是一个宏)。在使用防错代码时,一般判断为假的时候可以使用一些语句继续调试,而使用assert后,当判断为假貌似就直接结束程序了。所以我认为这是一个区别。还有就是在debug版的程序中可以允许assert,但是在release版中不应该出现assert,而防错代码应该是可以出现的,扩展的说,就是assert不能代替条件过滤。
还有什么区别?应该有吧,只是我实在是不知道了。多学多问,不耻下问,请高人指点啊!!!
11、对语句 if else 与操作符 ? : 使用场合的比较。(2分)
这个……其实?:就是if else的简化版,并没有太多的区别,如果说场合上有所不同的话,我各说一下它们为对方所不能的地方。
if else语句可以用在复杂的程序里,判断的结果为真时要执行N多程
序,这个时候如果用?:来写恐怕写出来的东西连自己亲妈都不认识了。所以?:操作符一般都是用在简单的条件判断中,虽然也可以嵌套和干一些别的,但是程序的可读性大大降低了。
但是操作符?:可以用在一个宏里,比如一个经典的面试题,定义一个宏MIN,比较两个数,输出小者,就可以这么写:#define MIN(A,B) ((A) <= (B) ? (A) : (B)) ,恐怕if else做不到吧。 具体还有什么场合上要注意的,我再想想看。
12、编写一个函数,输入一个的整型数字,可以选择按照8/10/16进制输出字符串。注意边界值。(5分)
char* transfer(int kind,unsigned int num) {
char *pt; switch(kind) {
case 8:
{sprintf(pt,\"%o\ case 10:
{sprintf(pt,\"%d\ case 16:
{sprintf(pt,\"%x\ default:
{sprintf(pt,\"%d\ }
return pt; }
随手写了一个,没有具体验证,大致意思就是函数transfer的两个输入参数为kind和num,kind是一个整型数,告诉函数transfer要转换
成多少进制的字符串,可以输入8、10、16,默认是10进制;num是一个无符号整型数,是被转换数。函数transfer将指针pt指向转换后的字符串地址,并将其返回。
需要说明的是,我不太清楚题目中是要输出到屏幕还是哪里,所以就做了一个返回值,如果要输出的屏幕也是很简单的事情。还有就是边界值的问题,我们首先要分清楚系统是多少bits的,其实这个边界值的判断应该在函数外完成。还有就是题目要注意,也没说怎么注意,我只是做了一下unsigned,算是注意了吧,其实如果超出范围了,注意了也没用。
13、本题(2分)。下面是一个16x16的黑白图标: static unsigned short stopwatch[] = { 0x07c6, 0x1ff7, 0x383b, 0x600c, 0x600c, 0xc006, 0xc006, 0xdf06, 0xc106, 0xc106, 0x610c, 0x610c, 0x3838, 0x1ff0, 0x07c0, 0x0000,
};
如何修改声明,可以使之在源代码中形象地表现出图形的模样。 我们需要明白一点,在做字模或者图形的时候,在黑白状态下,0代表白,1代表黑,那么好了,我们把字模变成二进制不就可以了。十六进制前缀是0x,二进制就是0b。以上面为例子,看看结果吧,是不是很有意思:
static unsigned short stopwatch[] = { 0b0000011111000110, 0b0001111111110111, 0b0011100000111011, 0b0110000000001100, 0b0110000000001100, 0b1100000000000110, 0b1100000000000110, 0b1101111100000110, 0b1100000100000110, 0b1100000100000110, 0b0110000100001100, 0b0110000100001100, 0b0011100000111000, 0b0001111111110000, 0b0000011111000000, 0b0000000000000000, };
14、说出可以使用calendar[11][30]变量的四种类型定义。(5分) 如:int calendar[12][31]; // 二维数组
首先,我们的脑子里在对待数组和指针的时候,应该将两者紧紧的联系起来!我们把二者结合起来通俗的讲,数组名的意思就是指向具有N
个连续字节空间的头地址;指针的意思就是指向的地址是具有N个连续字节空间的头地址,可以认为是一个数组名。不知道我说的够不够通俗,反正说完我反而晕了。
既然这样,如果一个一维数组可以用一个指针代替,那么二维数组就可以用一个指向指针的指针代替了。这也就是第一个答案: int **calendar;
如果处于这个思考,我们完全可以再扩展出两种方式:一种是一个指针指向一个一维数组;另一个是一个一维数组中有若干个指针。形象的定义出来就是我们要的第二种和第三种答案: int (*calendar)[31]; int *calendar[12];
好了,结合给出的int calendar[12][31];,不多不少正好四种,要是让我想第五种我都想不出来了。
最后想说一下,用指针方式变相定义数组的时候,一定要在定义后使用前分配空间,否则有可能空间是乱的,因为只告诉了编译器脑袋在哪里,并没告诉编译器身子有多长。
15、使用strcmp,当字符串相同时会返回'\\0'。但'\\0'一般作为逻辑假,因此下面的语句不容易理解:
if (!strcmp(s, \"string\")) return EQUATION; 如何经过简单修改,使之更易懂?(2分)
我不知道题目中说的'\\0'是哪来的,但是我明白题目的意思了,我来以我的理解翻译一下:
strcmp这个函数是用来比较字符串的,当两个字符串相同的时候,
返回值为0。但是一般情况下我们认为0是逻辑假,所以语句:if (!strcmp(s, \"string\")) return EQUATION; 让不懂strcmp的人比较纳闷:怎么要加“!”呢?因为加上!后,if才认为两个字符串是相同的。对于这个问题,理解了之后,再简单不过了:if (strcmp(s, \"string\")==0) return EQUATION; 。
16、编写一个自己的完全C语言版本的memset函数,并且评价这个实现的性能和可移植性。(5分)
memset这个函数的作用大致可以理解为将从指针指向的地址开始的指定字节长度的空间全部替换为指定的值。那么我们可以很轻松的用C来自己实现这个函数:
void *memset(void *position,void value,unsigned int count) {
void *head=position; while(count--) {
*(char *)position=(char)value; position=(char *)position+1; }
return head; }
相信大家都能写出来,只是方式不同罢了。在这个函数中,没有调用任何别的函数,完全靠C最基本的语句实现的,所以移植性应该很好,另外,在为memory的一个字节空间的set上,可以看到,除了while的递减外,只用到了两句话,可以说没法再精简了,所以性能应该说很好。至少我是这么觉得,嘿嘿。
17、在树和图这些数据结构中,通常使用指针来组织数据。如果我们要把这些数据保存到文件中,指针是没有意义的。我们该如何解决这个问题。(2分)
首先,这是一个面向对象的概念了。不写代码了,也没法写,就说说我的想法吧,想法最重要。
我记得我在上学的时候,C语言课上做过这么一个题目,大概意思是把一个数组中的数据写到文件中,然后再读回来恢复到数组中。实现的方法是用一个给定的分隔符将数组中各个数据分隔开,连分隔符一起写入文件,然后再读回来的时候识别出分隔符,将数据依次写入数组。具体实现的方法很多。
其实无论是树还是图,都是一种数据结构,只是多了些*lchild,*rchild,*next之类的指针,每一个struct其实都是一片连续的内存区域,也就是一串连续的数据,我们可以把这一串数据看做一个数据元素,如果为这些数据做一个索引,写入文件的时候我们就可以不去考虑指针的问题了,同样,从文件恢复数据的时候,按照这个索引重新建立指针就可以了。
这样的做法确实可行,但是如果自己去组织文件和结构显得很繁琐,所以我们可以利用数据库。有了数据库,我们就可以用数据库的列对应struct中的各个元素,如果struct中还有struct,那么就用关系型数据库,而且数据库有现成的ID索引(行),我们就不用自己造索引了,写数据库的时候按照数据的结构,写入数据库不同表的不同行中,读的
时候也一样,只是最后要重新建立指针罢了。
不知道我的想法是否成熟,但是我是这么做的,虽然实际应用的次数很少。关于对象序列化我真的没什么研究,因为一直在用C,没有怎么研究过C++,这类的问题在《Thinking in C++》中应该有阐述。还有,如果有更好的办法一定告诉我!!
18、用2种不同的方法计算long变量的\"1\"bit的个数。(2分)
时间有限,代码我就不写全了,只写关键代码,说一下我最先想到的思路:
第一种方法:
for(i=sizeof(long)*8;i>=0;i--) {if(num>>i&1) count++;}
这是用了位操作,先判断一个long型变量有多少位,然后不断位移进行与操作,如果位移后最低位是1,if为真,计数加一。如此循环,最后得到结果。
第二种方法: i=sizeof(long)*8; while(i) {
if(num%2) count++; num=num/2; i--; }
其实与前一种同出一辙,但是运用了除法和求余,原理差不多,不解释了。
其他的方法,我还想到了用typedef的位定义,虽然也是方法,不过挺麻烦的。至于别的,具体实现的方法太多了,原理其实都是差不多的。
19、任意给出一个C的基本数据类型,如何编码判断这个数据类型是有符号还是无符号的?(2分)
不得上机实验,写出下面代码的输出。解释这个行为是标准定义的,还是依赖实现的。(2分) int i;
for (i = 0; i < 10; i++) { int j = i;
printf (\"%d\\n\}
第一个问题:
这仿佛是一个微软的面试题,用宏的办法判断比较简单,代码如下: #define ISUNSIGNED(val) ((val)*0-1>0)
其实就是用到了有符号数可以小于0,但无符号数不可能小于0这个特性,不过这个宏对char或者其他类型的数据判断就无效了,当然谁也不会白痴到去判断一个char型的数据有没有符号。
第二个问题: 输出很简单: 0 1 2 3 4
5 6 7 8 9
至于标准定义还是依赖实现,我认为是ANSI C的标准定义,无论是for还是printf都是标准的ANSI C函数,因为当你运行这段代码的时候无需依赖任何头文件。
20、列出5种以上你所看过的C编程的书籍,并写简要书评。(5分) 对C的评价。如果要你改造一把菜刀,使之更加安全,你是否会使用这样的菜刀,为什么?(5分)
这个题目应该是最后放松的送分题了,没有标准答案,看的是个人的学习经历和对C的感悟。
我看过的C语言的书籍并不多,不到5本,看来为了凑数也要多看两本啊,现在让我深刻感受到了“书到用时方恨少”真正的含义! 我看过最多的,对我来说最重要的两本C语言的书是:老谭的《C程序设计》和两个米国人写的《新编C语言大全》。前者是“学电脑要从娃娃抓起”般的普及教材,同时也是各个中学、大学的C语言标准教材,内容相对比较少,而且没有涉及太深的知识,非常适合初学者;后者是我很久以前花了大价钱买的一本貌似很专业的书,确实比老谭写的详细的多,因为厚度就是老谭那本的两倍,很多东西都可以在那里找到。如果数据结构也能算的话,也算一本吧,感觉就是囫囵吞枣似的书籍,
应付考试用的。要是再来两本C++的书就完美了,可惜我真没看过,或者看过也忘了名字了,惭愧……
对于C的评价,我简单的说一下自己的感觉,C语言之所以30多年来长盛不衰,因为其灵活且紧凑的代码结构,丰富的运算符和数据结构的表达,加上无与伦比的硬件操作能力,使其完美的兼顾了高级语言和低级语言的特点,同时也极大扩展了应用范围。一个优秀的编译器可以使其代码的执行率直逼汇编语言,而且又具备汇编语言不可比拟的可移植性。但是其数据的封装和语法的不够严格,在这点上不如其他高级语言,如C++就改善了许多。虽然指针的引入是C语言划时代的进步,但是其不安全性也逐渐显现出来,虽然C++做了改进,将指针保留了下来,但是高级语言发展到JAVA和.NET架构的时代,已经彻底取消了指针,这样做虽然提高了安全性,但是我们永远不要忘记指针给我们带来的快捷与便利。
最后一个菜刀问题,我不是很明白题目想说什么,如果菜刀是C,改造的菜刀是C++,那么对于安全性的改进我们没有理由不接受,因为事实也是如此。在这个时刻讲究安全性的时代,改进是必然了的,但是我们不要忘了,任何所谓的安全性都是基于严谨的思维方式,即便是更安全的C++,也是C写出来的。菜刀只是一个工具,可以切菜也可以切手指头,关键是你的刀功和安全意识好不好,而不是一味强调工具是否安全。
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- huatuo0.com 版权所有 湘ICP备2023021991号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务