1、数组a[N],存放了1至N-1个数,其中某个数重复一次。写一个函数,找出被重复的数字.时间复杂度必须为o(N)函数原型:
答:正常裸机需要N的平方;但是存放的数据,是连续的1到N-1(不包含重复数,其和为N*(N-1)/2)。
int do_dup(int a[],int N) int do_dup(int a[],int N)//a[0]为监视哨
{ {
int i; int temp;
int s; while (a[0]!=a[a[0]])
int num; {
for(i=0;i<N;i++) temp=a[0];
s+=a[i]; a[0]=a[temp];
num=s-N*(N-1)/2;(num即为重复数) a[temp]=temp;
} }
return a[0];
}
2、写一个程序, 要求功能:求出用1,2,5这三个数不同个数组合的和为100的组合个数。
如:100个1是一个组合,5个1加19个5是一个组合。。。。 请用C++语言写。
答案:最容易想到的算法是:
设x是1的个数,y是2的个数,z是5的个数,number是组合数
注意到0<=x<=100,0<=y<=50,0<=z=20,所以可以编程为:
number=0;
for (x=0; x<=100; x++)
for (y=0; y<=50; y++)
for (z=0; z<=20; z++)
if ((x+2*y+5*z)==100)
number++;
3、内存对齐问题的原因?
平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据;
性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐,因为为了访问未对齐的内存,处理器需要做两次内存访问,
而对齐的内存访问仅需要一次。
4、比较一下进程和线程的区别?
(1)、调度:线程是CPU调度和分派的基本单位
(2)、拥有资源:
* 进程是系统中程序执行和资源分配的基本单位
* 线程自己一般不拥有资源(除了必不可少的程序计数器,一组寄存器和栈),但他可以去访问其所属进程的资源,
如进程代码,数据段以及系统资源(已打开的文件,I/O设备等)。
(3)系统开销:
* 同一进程中的多个线程可以共享同一地址空间,因此它们之间的同步和通信的实现也比较简单
* 在进程切换的时候,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置;
而线程切换只需要保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作,从而能更有效地使用系统资源和
提高系统吞吐量。
5、判断下方程序结果输出(考指针偏移)
main() {
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
}则输出__
答:2,5。 &a相当于变成了行指针,加1则变成了下一行首地址;而*(a+1)就是a[1],*(ptr-1)就是a[4],执行结果是2,5
6、判断下方程序执行结果(考察动态内存)
void getmemory(char *p)
{
p=(char *) malloc(100);
strcpy(p,"hello world");
}
int main( )
{
char *str=NULL;
getmemory(str);
printf("%s/n",str);
free(str);
return 0;
}
程序崩溃,getmemory中的malloc 不能返回动态内存, free()对str操作很危险
同样的考题,被考察了很多次,这是一个高频考点!可以再多看几个类似的考题,如下:
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
请问运行Test函数会有什么样的结果?
答:程序崩溃。因为GetMemory并不能传递动态内存,Test函数中的 str一直都是 NULL。strcpy(str, "hello world");将使程序崩溃。
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
请问运行Test函数会有什么样的结果?
答:(1)能够输出hello
(2)内存泄漏
分析:这个一个考验对指针理解的题目,上面程序在运行之后:
1,调用GetMemory( str )后, str并未产生变化,依然是NULL.只是改变的str的一个拷贝的内存的变化 : GetMemory(char *p)中的 p “就是” GetMemory(str)中的str。但p“不是”str,它只是“等于”str 。
就象: int a = 100;
int b = a; // 现在b等于a
b = 500; // 现在能认为a = 500 ?
显然不能认为a = 500,因为b只是等于a,但不是a! 当b改变的时候,a并不会改变,b就不等于a了。 因此,虽然p已经有new的内存,但str仍然是null
2,strcpy( str, "hello world" );程序运行到这将产生错误;
3,new的时候有可能内存出错,应该在*p = (char *) malloc( num ); 后判断内存是否申请成功,应加上:
if ( *p == NULL )
{
...//进行申请内存失败处理
}
4,动态创建的内存没释放。
7、 请问运行Test函数会有什么样的结果?(考查栈的知识)
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
答:可能是乱码。因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是 NULL,
但其原现的内容已经被清除,新内容不可知。
8、请问运行Test函数会有什么样的结果?(考查野指针)
void Test(void) {
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
答: 结果难以预料 ;
篡改动态内存区的内容,后果难以预料,非常危险。因为free(str);之后,str成为野指针,
if(str != NULL)语句不起作用。
野指针不是NULL指针,是指向被释放的或者访问受限内存指针。
造成原因:指针变量没有被初始化任何刚创建的指针不会自动成为NULL;
指针被free或delete之后,没有置NULL;
指针操作超越了变量的作用范围,比如要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。
9、请解答下方问题(地址偏移)
unsigned char *p=(unsigned char *)0x0801000;
unsigned char *q=(unsigned char *)0x0810000;
p+5 =? 答:0x0801005
q+5 =? 答: 0x0810005
10、线程同步的方法有哪些?
答:信号量、条件变量、互斥锁
11、进程间通信有哪几种方式
管道、命名管道、消息队列、共享内存、信号、信号量、套接字
(1)、 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。
进程的亲缘关系通常是指父子进程关系。
(2)、有名管道 (named pipe) :有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
(3)、信号量( semophore ) :信号量是一个计数器,可以用来控制多个进程对共享资源的访问。
它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。
因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
(4)、消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。
消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
(5)、信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
(6)、共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,
这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,
如信号两,配合使用,来实现进程间的同步和通信。
(7)、套接字( socket ) : 套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
12、宏和函数的优缺点?
(1)、函数调用时,先求出实参表达式的值,然后带入形参。而使用带参数的宏只是进行简单的字符替换。
(2)、函数调用是在程序运行时处理的,分配临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分配内存单元,
不进行值的传递处理,也没有“返回值”的概念。
(3)、对函数中的实参和形参都要定义类型,二者的类型要求一致,应进行类型转换;而宏不存在类型问题,宏名无类型,
它的参数也是无类型,只是一个符号代表,展开时带入指定的字符即可。宏定义时,字符串可以是任何类型的数据。
(4)、调用函数只可得到一个返回值,而宏定义可以设法得到几个结果。
(5)、使用宏次数多时,宏展开后源程序长,因为每次展开一次都使程序增长,而函数调用不使源程序变长。
(6)、宏替换不占运行时间,只占编译时间;而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。
13、C和c++的不同
c和c++的一些不同点(从语言本身的角度):
1)c++源于c,c++最重要的特性就是引入了面向对象机制,class关键字。
2)c++中,变量可以再任何地方声明;c中,局部变量只能在函数开头声明。
3)c++中,const型常量是编译时常量;c中,const常量只是只读的变量。
4)c++有&引用;c没有
5)c++的struct声明自动将结构类型名typedef;c中struct的名字只在结构标签名字空间中,不是作为一种类型出现
6)c语言的main函数可以递归调用;c++中则不可以
7)c中,void *可以隐式转换成其他指针类型;c++中要求现实转换,否则编译通不过
14、大小端格式问题。
方法一:
void checkCpuMode(void)
{
int i = 0x12345678;
char *cp = (char *)&i;
if(*cp == 0x78)
printf("little endian");
else
printf("big endian\n");
}
方法二:
void checkCpuMode(void)
{
int a = 0x12345678;
if((char)a == 0x12)
printf("big endian\n");
else
printf("little endian\n");
}
方法三:
void checkCpuMode(void)
{
union
{
short s;
char c[sizeof(short)];
}un;
un.s=0x0102;
if(un.[0]==1&&un.c[1]==2)
printf("big endian\n");
else
printf("little endian\n");
}
15、由C/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack): 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap): 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。
注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(static): 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,
未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后有系统释放 。
4、文字常量区: 常量字符串就是放在这里的, 程序结束后由系统释放。
5、程序代码区: 存放函数体的二进制代码。
16、输出下方代码的结果(考查标准输入的格式)
sscanf("123456 ", "%4s", buf); 1234
sscanf("123456 asdfga","%[^ ]", buf); 123456
sscanf("123456aafsdfADDEFF", "%[1-9a-z]", buf); 123456aafsdf
sscanf("123afsdfADJKLJ", "%[^A-Z]", buf); 123afsdf
17、评价下方代码(常串变量赋值问题)
char* s="AAA";
s[0]='B';
printf("%s",s);
有什么错?
答:"AAA"是字符串常量。s是指针,指向这个字符串常量,所以声明s的时候就有问题。
cosnt char* s="AAA";
然后又因为是常量,所以对是s[0]的赋值操作是不合法的。
17、如何用C语言实现读写寄存器变量
// 假设寄存器的地址是固定的,这里使用一个宏定义
#define REGISTER_ADDRESS 0x12345678
// 定义一个指向寄存器地址的指针
volatile uint32_t* register_ptr = (volatile uint32_t*)REGISTER_ADDRESS;
// 读取寄存器
uint32_t read_register() {
return *register_ptr;
}
// 写入寄存器
void write_register(uint32_t value) {
*register_ptr = value;
}
18、如下语句5["abcdef"]能够编译通过,请问编译后的结果是什么?
printf("%d\n",5["abcdef"]);输出'f'的ACSII值,如果是4["abcdef"]则输出'e'的ACSII的值。
19、输出下方代码执行结果(条件逗号表达)
for(i=0;i<2,i<3,i<4;i++)
printf("%d \n",i);
答:输出:0,1,2,3。
20、物理地址,虚拟地址,逻辑地址和总线地址的区别
逻辑地址(Logical Address)是指由程序产生的与段相关的偏移地址部分。
例如,你在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,
它是相对于你当前进程数据段的地址,不和绝对物理地址相干。只有在Intel实模式下,
逻辑地址才和物理地址相等(因为实模式没有分段或分页机制, Cpu不进行自动地址转换);
逻辑也就是在Intel 保护模式下程序执行代码段限长内的偏移地址(假定代码段、数据段如果完全一样)。
应用程序员仅需与逻辑地址打交道,而分段和分页机制对您来说是完全透明的,仅由系统编程人员涉及。
应用程序员虽然自己可以直接操作内存,那也只能在操作系统给你分配的内存段操作。
线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,
或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,
那么线性地址可以再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。
Intel 80386的线性地址空间容量为4G(2的32次方即32根地址总线寻址)。
物理地址(Physical Address) 是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。
如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。
如果没有启用分页机制,那么线性地址就直接成为物理地址了。
在x86下,外设的i/o地址是独立的,即有专门的指令访问外设i/o,i/o地址就是你所说的“总线地址”。
而“物理地址”就是ram地址。在arm中,i/o和ram统一编址,但linux为了统一各个平台,仍然保留这个概念,其实就是物理地址。
21、编写内核程序中申请内存和编写应用程序时申请内存有什么区别
应用程序使用C函数库中的内存分配函数malloc();申请内存内核会为进程使用的代码和数据空间维护一个当前位置的值brk,
这个值保存在每个进程的数据结构中。它指出了进程代码和数据(包括动态分配的数据空间)在进程地址空间中的末端位置。
当malloc()函数为程序分配内存时,它会通过系统调用brk()把程序要求新增的空间长度通知内核,
内核代码从而可以根据malloc()所提供的信息来更新brk的值,但此时并不为新申请的空间映射物理内存页面。
只有当程序寻址到某个不存在对应物理页面的地址时,内核才会进行相关物理内存页面的映射操作。
当用户使用内存释放函数free()动态释放已申请的内存块时,c库中的内存管理函数就会把所释放的内存块标记为空闲,
以备程序再次申请内存时使用。在这个过程中内核为该进程所分配的这个物理页面并不会被释放掉。
只有当进程最终结束时内核才会全面收回已分配和映射到该进程地址空间范围内的所有物理内存页面。