C/C++一些问题的理解

1. 关键字volatile有什么含意?并给出三个不同的例子。

 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

(1)并行设备的硬件寄存器(如:状态寄存器)

(2)一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

(3) 多线程应用中被几个任务共享的变量

回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。

  1. 一个参数既可以是const还可以是volatile吗?解释为什么。

    答:是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

  2. 一个指针可以是volatile 吗?解释为什么。

    答:是的。尽管这并不很常见。一个例子是当一个中断服务子程序修该一个指向一个buffer的指针时。

  3. 下面的函数有什么错误:

    int square(volatile int *ptr)

{

return *ptr * *ptr;

}

答:这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr)

{

int a,b;

a = *ptr;

b = *ptr;

return a * b;

}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)

{

int a;

a = *ptr;

return a * a;

}

 

2. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。

这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:

int *ptr;

ptr = (int *)0x67a9;

*ptr = 0xaa55;

A more obscure approach is:

一个较晦涩的方法是:

*(int * const)(0x67a9) = 0xaa55;

 

3. 表达式中如果同时存在有符号数和无符号数的话,有符号数会被提升为无符号数

获取数组长度

template <typename T, size_t N>

size_t arrarysize(T (&array)[N]) { return N; }

4. 数组指针与多维数组的理解

 

一般形式

typedef定义

指针数组

int *a[10];

typedef int *t;

t a[10];

数组指针

int (*a)[10];    

typedef int t[10];

t *a;

例如:

(1)int a[10];

int (*pa)[10]=&a;

a是一个数组,在&a这个表达式中,数组名做左值,取整个数组的首地址赋给指针pa。注意,&a[0]表示数组a的首元素的首地址,而&a表示数组a的首地址,显然这两个地址的数值相同,但这两个表达式的类型是两种不同的指针类型,前者的类型是int *,而后者的类型是int (*)[10]。*pa就表示pa所指向的数组a,所以取数组的a[0]元素可以用表达式(*pa)[0]。注意到*pa可以写成pa[0],所以(*pa)[0]这个表达式也可以改写成pa[0][0],pa就像一个二维数组的名字,它表示什么含义呢?下面把pa和二维数组放在一起做个分析。

(2)int a[5][10];

int (*pa)[10] = &a[0];

则pa[0]和a[0]取的是同一个元素,唯一比原来复杂的地方在于这个元素是由10个int组成的数组,而不是基本类型。这样,我们可以把pa当成二维数组名来使用,pa[1][2]和a[1][2]取的也是同一个元素,而且pa比a用起来更灵活,数组名不支持赋值、自增等运算,而指针可以支持,pa++使pa跳过二维数组的一行(40个字节),指向a[1]的首地址。

(3) int a[2][3][5];

int (*pa)[5]=a[0];

int *ppa=a[0][0];

a是表示a+0,是指向第一个二维数组的指针,

a[0]表示a[0]+0,是指向第一个一维数组的指针

a[0][0]表示a[0][0]+0,是指向一维数组的第一个元素的指针

a[0][0][0]表示int,是一维数组的第一个元素

5. C++中的多态的理解

C++中多态的实现要求基类的相应的函数要定义为virtual。

可以这样理解:我们有这样的需求,就是要用子类的方法去覆盖基类的某些方法,所以,为了实现这个目标,第一步就是子类与基类之间的函数的原型是一样的。第二步就是将基类中的该方法声明为virtual。这样做其实是为了通知编译器,说这个函数我要实行多态,你在生成子类的虚函数表(v-table)的时候注意,要用子类的函数的地址替代(覆盖)基类的函数的地址。

7. C语言注意事项

(1)scanf和printf的返回值

scanf()函数的返回值为按照要求格式读取量的个数。当读取错误是(即都没读取成功),则返回0;当遇到错误时则返回EOF;

printf()函数的返回值为输出量的个数。返回值都为一个整型数

(2)sizeof的容易被忽略的用法

sizeof可以返回字符串常量的长度

如:sizeof("AB")=3

char s[]="abc";sizeof(s)=4

下面是网上找到的:

对于char str[] = "abcdef";就有sizeof(str) == 7,因为str的类型是char[7],

也有sizeof("abcdef") == 7,因为"abcdef"的类型是const char[7]

对于char *ptr = "abcdef";就有sizeof(ptr) == 4,因为ptr的类型是char*。

对于char str2[10] = "abcdef";就有sizeof(str2) == 10,因为str2的类型是char[10]。

对于void func(char sa[100],int ia[20],char *p);

就有sizeof(sa) == sizeof(ia) == sizeof(p) == 4,因为sa的类型是char*,ia的类型是int*,p的类型是char*。

(3) C语言中如何判断文件是否存在

用函数access,头文件是io.h,原型:

int access(const char *filename, int amode);

amode参数为0时表示检查文件的存在性,如果文件存在,返回0,不存在,返回-1。

这个函数还可以检查其它文件属性:

06 检查读写权限

04 检查读权限

02 检查写权限

01 检查执行权限

00 检查文件的存在性

在UNIX和VC下实验成功。

好处是 :fopen(..,"r")不好,当无读权限时一不行了。而这个就算这个文件没有读权限,也可以判断这个文件存在于否:存在返回0,不存在返回-1

#include <stdio.h>

int main()

{

printf ("%d",access("111",0));

}

C语言中创建一个目录system("mkdir D:\\abc\def");

 

8. C++语言中extern C的作用

因为C语言和 C++ 语言的编译规则不一样,所以要告诉系统哪些函数是用 C 方式编译,哪些函数需要用 C++ 方式编译。 如果你不加 extern "C" ,在编译时,系统会提示找不到此函数。

extern "C"表示编译生成的内部符号名使用C约定

例如: int Fun(int i,int j)

C:_Fun

C++:_Fun_int_int

具体生成什么可能与编译器有关 。由于C++支持重载,而重载是在编译期确定的,所以C++必须在内部符号名上区分各重载函数,所以就将参数类型加在函数名后

9.C++中为什么用模板类。

答:(1)可用来创建动态增长和减小的数据结构

(2)它是类型无关的,因此具有很高的可复用性。

(3)它在编译时而不是运行时检查数据类型,保证了类型安全

(4)它是平台无关的,可移植性

(5)可用于基本数据类型

10. C++中近堆与远堆问题

应该是near 和 far指针的问题。

产生原因是这样的:如果动态创建的数据量比较大,用一个数据段(一般是64K,此时段指针不变,偏移量指针在16bit内变化)放不下的时候,需要重新开辟一个数据段供存放更多的数据,此时称原来的堆部分为近堆,改变段地址后的新的数据段所在的堆部分称为远堆

PC机得存储器地址是由段地址和偏移地址组合而成,每一段不能超过64k字节地址,因而统一个段内的地址存取,用偏 移地址就可以实现,因段地址寄存器所存的段地址是不变。当用指针时,只16未就够了,这一类就是near指针。当要在另一个段内取数据,就要跨越段,既要 指明存取段的段地址和偏移地址,这时段地址寄存器所存段地址要改变,在使用指针指向另一个段内地址时,就要用32位表示,就是far指针了。

由于dos是16位的操作系统,故程序代码受到segment的限制,near指针最多只能指向64k以内的代码或数据。而far则可以跨段寻址,如果我 没记错的话好像还有huge类型的远指针。但是由于watcom,djgpp等dos下的编译器,以及所有windows下的编译器都是32位的顾没有 64k的限制,指针也就不分远近了

      

posted @ 2012-03-27 20:54  Mr.Rico  阅读(...)  评论(...编辑  收藏