话不多说开始,看题目。

简答题

memcpy和strcpy的区别

  1. 复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。

  2. 复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符”\0”才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。

  3. 用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。

函数原形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

char * strcpy(char * dest, const char * src) // 实现src到dest的复制
{
  if ((src == NULL) || (dest == NULL)) //判断参数src和dest的有效性
  {
   return NULL;
  }
  char *strdest = dest; //保存目标字符串的首地址
  while ((*strDest++ = *strSrc++)!='\0'); //把src字符串的内容复制到dest下
  return strdest;
}


void *memcpy(void *memTo, const void *memFrom, size_t size)
{
  if((memTo == NULL) || (memFrom == NULL)) //memTo和memFrom必须有效
return NULL;
  char *tempFrom = (char *)memFrom; //保存memFrom首地址
  char *tempTo = (char *)memTo; //保存memTo首地址
  while(size -- > 0) //循环size次,复制memFrom的值到memTo中
  *tempTo++ = *tempFrom++ ;
  return memTo;
}

楼主在写的时候,发现自己没有完全记住这些用法,在编写上还是比较少用这些基础的函数,导致只有迷糊的印象,在工作中有遇到几次,但是没有记熟练。

信号量与互斥锁的区别

信号量:那是多线程同步用的,一个线程完成了某一个动作就通过信号告诉别的线程,别的线程再进行某些动作。

互斥量:这是多线程互斥用的,比如说,一个线程占用了某一个资源,那么别的线程就无法访问,知道这个线程离开,其他的线程才开始可以利用这个资源。

1):互斥量用于线程的互斥,信号线用于线程的同步。这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

(2):互斥量值只能为0/1,信号量值可以为非负整数。

楼主在写的时候,隐隐约约记得这些含义,但是表述比较模糊不清。

简述程序编译的过程

预处理阶段。预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如hello.c中第一行的#include<stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中,结果就得到了另一个C程序,通常是以.i作为文件扩展名。

编译阶段。编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。汇编语言程序中的每条语句都以一种标准的文本格式确切的描述了一条低级机器语言指令。

汇编阶段。汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种可重定位目标程序的格式,并将结果保存在目标文件hello.o中。hello.o文件是一个二进制文件,它的字节编码是机器语言指令而不是字符,如果我们在文本文件中打开hello.o文件,看到的将是一堆乱码。

链接阶段。链接器(ld)负责处理合并目标代码,生成一个可执行目标文件,可以被加载到内存中,由系统执行
楼主在解答的时候,写是的吧初始化那些变量个压入bss段、文本段、数据段这些里,总体来说,方向出了一些错误,只讲述了其中编译的阶段。

程序编译流程

语句for(;1;)有什么问题?它是什么意思?
类似while(1),死循环的意思

它们的汇编指令都是一样的,所以效率也是一样的

单片机的编程中经常用到while(1)死循环来进行轮寻操作,但分析Linux内核源代码时却经常见到for(;;)作为死循环的条件。

两者区别:

for(;;)死循环里的两个;;代表两个空语句,编译器一般会优化掉它们,直接进入循环体。

while(1)死循环里的1被看成表达式,每循环一次都要判断常量1是不是等于零。

do……while和while……do有什么区别?

前一个循环一遍再判断,后一个判断以后再循环。

选择题

MMU的作用(A、B)

A:内存保护

B:地址转换

C:加快存取速度

D:安全保密

E:内存分配

楼主有看过MMU但是记错了,选择了B与E。

  • 程序中使用的地址均是虚拟内存地址,进程中的数据是如何进入到物理内存中的呢?
  • MMU完成虚拟内存到物理内存的映射,即虚拟地址映射为物理地址;
  • 流水线中预取指令取到的地址是虚拟地址,需要MMU转换以及设置访问权限。

以下属于DMA特点的有(B、C)

A:占用CPU

B:占用总线

C:不占用CPU

D:不占用总线

楼主在解答时,总线的概念有些忘记:

总线是一种内部结构,它是cpu、内存、输入、输出设备传递信息的公用通道,主机的各个部件通过总线相连接,外部设备通过相应的接口电路再与总线相连接,从而形成了计算机硬件系统。在计算机系统中,各个部件之间传送信息的公共通路叫总线,微型计算机是以总线结构来连接各个功能部件的。

在Linux内核中,总线属于核心层,是纯软件的东西。在Linux内核中总线左手牵着驱动右手牵着设备,总线的工作就是就是完成总线下的设备和驱动之间的匹配。也就是在左手中找到与右手相匹配的设备驱动,并完成他们之间的匹配。

描述题

全局变量、局部变量、静态全局变量、静态局部变量的区别和引用方式?

初始化的全局变量在.data段,可以外部文本加extern引用;
未初始化的全局变量在.bss段,可以外部文本加extern引用;
静态全局变量区别是不能在外部文件引用;
局部变量在运行时,栈区分配空间;
静态局部变量在静态区分配空间,函数调用后内存不释放;

内存的分配方式有哪三种,请简单介绍一下。

内存分配有三种:静态存储区、堆区和栈区。他们的功能不同,他们使用方式也就不同。

  1. 静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。
  2. 栈区:在执行函数时,函数(包括main函数)内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。(任何变量都处于站区,例如int a[] = {1, 2},变量a处于栈区。数组的内容也存在于栈区。)
  3. 堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,并立即将指针置位NULL,防止产生野指针。

野指针是如何产生的?如何避免野指针?

指向非法的内存地址指针叫作野指针(Wild Pointer),也叫悬挂指针(Dangling Pointer),意为无法正常使用的指针。

产生:使用未初始化的指针、指针所指向的对象已经消亡、指针释放后未置空

避免:(1)C++引入了引用机制,如果使用引用可以达到编程目的,就可以不必使用指针。因为引用在定义的时候,必须初始化,所以可以避免野指针的出现。

​ (2)如果一定要使用指针,那么需要在定义指针变量的同时对它进行初始化操作。定义时将其置位NULL或者指向一个有名变量。

​ (3)对指针进行free或者delete操作后,将其设置为NULL。对于使用 free 的情况,常常定义一个宏或者函数 xfree 来代替 free 置空指针

#define xfree(x) free(x);x = NULL;

C++中指针和引用的区别

指针是一个变量,存储的是一个地址,指向内存的一个存储单元;

引用是原变量的一个别名,跟原来的变量实质上是同一个东西。

1
2
3
int a = 996;
int *p = &a; // p是指针, &在此是求地址运算
int &r = a; // r是引用, &在此起标识作用
  1. 引用必须被初始化,但是不分配存储空间。指针不声明时初始化,在初始化的时候需要分配存储空间。

  2. 引用初始化后不能被改变,指针可以改变所指的对象。

  3. 不存在指向空值的引用,但是存在指向空值的指针。

    C++中指针与引用的区别 - 知乎 (zhihu.com)

    楼主答题时,完全不知道这个概念,后续会继续加强C++相关的知识储备。

查错题

下面程序会出现什么结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <string.h>
#include <stdio.h>
#include <malloc.h>
void getmemory(char *p)
{
p = (char *)malloc(100);
strcpy(p,"hello worl");
}
int main()
{
char *str = NULL;
getmemory(str);
printf("%s \r\n",str);
free(str);
return 0;
}

上述程序执行完,是会打印出空值,因为函数中的局部变量已经被释放了,

getmemory()运行完,已经被哦释放了。

正确修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <string.h>
#include <stdio.h>
#include <malloc.h>
void getmemory(char *p)
{
p = (char *)malloc(100);
strcpy(p,"hello worl");
printf("%s \r\n",p);
free(p);
p = NULL;
}
int main()
{
char *str = NULL;
getmemory(str);
return 0;
}

下面这个程序执行后会有什么错误或者结果

1
2
3
4
5
6
7
8
9
10
11
12
#define MAX 255
int main()
{
unsigned char A[MAX],i;
for(i = 0;i<= MAX;i++)
{
A[i] = i;
printf("A[%d] = %d\r\n",i,i);
}
return 0;
}

一直在循环中,原因是因为unsigned char 范围是02^8-1 (0255),当溢出后,会进行(因为0xff + 1是256,与2^8求模后就是0)求模赋值,又会重0开始运行,因此无法超过最大值。

楼主答题时,没有考虑到这一点,很纠结,突然不知道哪里出现了问题。

请问一下程序将输出什么结果

1
2
3
4
5
6
7
8
9
10
11
char *RetMenory(void)
{
char p[] = "hello world";
return p;
}
void main()
{
char *str =NULL;
str = RetMenory();
printf(str);
}

也是局部变量的问题,函数RetMenory()返回的char * 的数据以及呗释放掉了,返回的指针类型。

修改如下:

1
2
3
4
5
6
7
8
9
10
11
char *RetMenory(void)
{
static char p[] = "hello world";
return p;
}
void main()
{
char *str =NULL;
str = RetMenory();
printf(str);
}

修改为静态局部变量,则不会随函数的结束而释放。

已知strcpy的函数原形

1
2
3
4
5
6
7
8
9
10
char * strcpy(char * strDest, const char * strSrc) // 实现strSrc到strDest的复制
{
  if ((src == NULL) || (dest == NULL)) //判断参数src和dest的有效性
  {
   return NULL;
  }
  char *strdest = dest; //保存目标字符串的首地址
  while ((*strDest++ = *strSrc++)!='\0'); //把src字符串的内容复制到dest下
  return strdest;
}

不调用库函数,实现strcpy函数并解释为什么要返回char *

1
2
3
4
5
6
7
8
9
10
11
12
char *strcpy(char *strDest,const char *strSrc)
{
if(strDest == NULL || strSrc == NULL)
return NULL;
char *p = strDest;
while(*strSrc != '\0')
{
*p++ = *strSrc++;
}
*p = *strSrc; //'\0'
return strDest;
}

为什么需要返回char *

有时候函数原本不需要返回值,但为了增加灵活性支持链式表达,可以附加返回值。
int length = strlen( strcpy(str, “Hello World”) );

其实,说白了,就是如果上面的字符串拷贝函数strcopy的返回值是void,那么,上面那句:

int length = strlen( strcpy( strDest, “hello world”) );

就要像上面那位的回答,写成好几句了:

char strDest[12];

strcpy( strDest, “hello world”);

int length = strlen(strDest);

而这种直接返回char *的手段,就是为了后来函数调用者方便而设计的.不用你这么麻烦用上述方法去使用了,而直接可以使用拷贝后的dest字符串了.这种方便的实现方法,看起来就是链子链在一起的,所以称为 链式表达式

链表实现:
后续补充

感悟总结

这套题目,结合了嵌入式的开发,主要涉及了C\C++、Linux相关的方面的问题,在答题饿过程中,也总结了自己的不足之处,例如对一C++的概念不够了解、Linux一些相关属性,明白什么意思,但是语言组织不起来、C中存在饿一些细节把握不够到位,整体上还需要平时的注重。后续将会持续进一步学习,开发。