飞道的博客

C语言再学习 -- 嵌入式面试知识点总结

304人阅读  评论(0)

如需转载请注明出处:https://juyou.blog.csdn.net/article/details/115716559

之前有写过 日常生活 – 嵌入式面试 ,讲了面试大部分都会问哪些问题。
也有自己总结了一些面试题:

但是每次我都要翻好几篇文章挨个看知识点,这就很烦了。现在将所用到的知识点在这篇文章内加以总结。

一、关键字

1、const

问题:

  • 问题一:const有什么用途?(请至少说明两种)
    (1)可以定义const常量
    (2)const可以修饰函数的参数和返回值,甚至函数的定义体。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
  • 问题二:以下的p和*p哪个可变,哪个不可变?
    先忽略类型名(编译器解析的时候也是忽略类型名),我们看 const 离哪个近。“近水楼先得月”,离谁近就修饰谁。
    int arr[5];
    const int * p = arr; //const 修饰 * p,p 是指针,可变; * p 是指针指向的对象,不可变。
    int const * p = arr; //const 修饰 * p,p 是指针, 可变; * p 是指针指向的对象,不可变。
    int * const p = arr; //const 修饰 p, p 是指针,不可变; p 指向的对象可变。
    const int * const p= arr; //前一个 const 修饰 * p,后一个 const 修饰 p,指针 p 和 p 指向的对象都不可变。
  • 问题三:关键字const有什么含义?
    const修饰的数据类型是指常类型,常类型的变量或对象的值是不能被更新的。或者说const意味着只读。

解答:

参看:C语言再学习 – 关键字const
const 修饰类型:
1)const 修饰一般常量
2)const修饰指针、数组
3)const 修饰函数的形参和返回值
4)const 修饰常对象
5)const 修饰常引用
6)const 修饰类的成员变量
7)const 修饰类的成员函数

const 作用:
1)可以定义 const 常量,具有不可变性。
2)便于进行类型检查,使编译器对处理内容有更多了解,消除一些隐患。
3)可以避免意义模糊的数字出现,同样可以很方便进行参数的调整和修改。同宏定义一样,可以做到不变则已,一变都变。
4)可以保护被修改的东西,防止意外的修改,增强程序的健壮性。
5)可以节省空间,避免不必要的内存分配。
6)为函数重载提供了一个参考
7)提高效率

const介绍:
1)在定义该const 变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为 const,或二者同时指定为const;
3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
4)对于类的成员函数,若指定其为const 类型,则表明其是一个常函数,不能修改类的成员变量;
5)对于类的成员函数,有时候必须指定其返回值为const 类型,以使得其返回值不为“左值”。
作用的话,可以保护被修改的东西,防止意外的修改,增强程序的健壮性。

2、static

问题:

  • 问题一:关键static的作用是什么?
    static 修饰全局变量
    static 修饰局部变量
    static 修饰函数

解答:

参看:C语言再学习 – 存储类型关键字
1)static 修饰的全局变量也叫静态全局变量,该类具有静态存储时期、文件作用域和内部链接,仅在编译时初始化一次。如未明确初始化,它的字节都被设定为0。static全局变量只初使化一次,是为了防止在其他文件单元中被引用;利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。
2)static 修饰的局部变量也叫静态局部变量,该类具有静态存储时期、代码作用域和空链接,仅在编译时初始化一次。如未明确初始化,它的字节都被设定为0。函数调用结束后存储区空间并不释放,保留其当前值。
3)static 修饰的函数也叫静态函数,只可以在定义它的文件中使用。

3、volatile

问题:

  • 问题一:关键字volatile有什么含意?并给出三个不同的例子。
    volatile关键字是一种类型修饰符。volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。直接读值是指从内存重新装载内容,而不是直接从寄存器拷贝内容。
    volatile使用:
    (1)并行设备的硬件寄存器(如:状态寄存器)。
    (2)一个中断服务子程序中会访问到的非自动变量。
    (3)多线程应用中被几个任务共享的变量。

解答:

参看:C语言再学习 – 关键字volatile

4、sizeof

问题:

  • 问题一:以下为Windows NT下的32位C++程序,请计算sizeof的值。
    void Func ( char str[100])
    {
    请计算:
    sizeof( str ) = 4
    }
    char str[] = “Hello” ;
    char *p = str ;
    int n = 10;
    请计算:
    sizeof (str ) = 6
    sizeof ( p ) = 4
    sizeof ( n ) = 4
    void *p = malloc( 100 );
    请计算:
    sizeof ( p ) = 4
  • 问题二:在 32 位系统下:short * p =NULL;sizeof§的值是多少?sizeof( * p)呢?
    sizeof ( p ) = 4; 因为 p为指针,32位系统 指针所占字节为 4个字节
    sizeof ( *p ) = 2; 因为 *p 为 指针所指向的变量为int类型,short为 2个字节
  • 问题三:请计算sizeof的值
    int a[100];
    sizeof (a) = 400; // 因为 a是类型为整型、有100个元素的数组,所占内存为400个字节
    sizeof (a[100]) = 4; //因为 a[100] 为数组的第100元素的值该值为 int 类型,所占内存为4个字节。
    sizeof (&a) = 4; //因为 &a 为数组的地址即指针,32位系统 指针所占字节为 4个字节
    sizeof (&a[0]) = 4; //因为&a[0] 为数组的首元素的地址即指针,32位系统 指针所占字节为 4个字节

解答:

参看:C语言再学习 – 关键字sizeof与strlen
1)sizeof 操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定。
在windows,32位系统中
char 1个字节
short 2个字节
int 4个字节
long 4个字节
double 8个字节
float 4个字节
2)数据类型必须用圆括号括住。如:sizeof (int)

记住这两句话:
在 32 位系统下,不管什么样的指针类型,其大小都为 4 byte。
参数传递数组永远都是传递指向数组首元素的指针。

5、extern

问题:

  • 问题一:在C++程序中调用被C编译器编译后的函数,为什么要加extern “C”声明?
    C++语言支持函数重载, C 语言不支持函数重载。函数被 C++编译后在库中的名字与 C 语言的不同。假设某个函数的原型为: void foo(int x, int y);该 函 数 被 C 编 译 器 编 译 后 在 库 中 的 名 字 为 _foo, 而 C++编 译 器 则 会 产 生 像_foo_int_int 之类的名字。C++提供了 C 连接交换指定符号 extern“ C”来解决名字匹配问题。

解答:

参看:C语言再学习 – 存储类型关键字
C 程序中,不允许出现类型不同的同名变量。而C++程序中 却允许出现重载。重载的定义:同一个作用域,函数名相同,参数表不同的函数构成重载关系。因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,来解决名字匹配问题。简单来说就是,extern “C”这个声明的真实目的是为了实现C++与C及其它语言的混合编程。

二、字符串

1、strcpy 函数功能实现

问题:

  • 问题一:编写 strcpy 函数
    已知 strcpy 函数的原型是
    char *strcpy(char *strDest, const char *strSrc);
    其中 strDest 是目的字符串, strSrc 是源字符串。
    ( 1)不调用 C++/C 的字符串库函数,请编写函数 strcpy
    char *strcpy(char *strDest, const char *strSrc);
    {
    assert((strDest!=NULL) && (strSrc !=NULL)); // 2分
    char *address = strDest; // 2分
    while( (*strDest++ = * strSrc++) != ‘\0’ ) // 2分
    NULL ;
    return address ; // 2分
    }
    ( 2) strcpy 能把 strSrc 的内容复制到 strDest,为什么还要 char * 类型的返回值?
    答:为了实现链式表达式。 // 2 分
    例如 int length = strlen( strcpy( strDest, “hello world”) );

解答:

参看:C语言再学习 – 字符串和字符串函数
剩下的像strcat、strlen、atoi、itoa函数功能实现,自行查看。

三、大小端

问题:

  • 问题一:C语言实现大小端
#include <stdio.h>
#icnlude <arpa/inet.h>
int main (void)
{
	union
	{
		short i;
		char a[2];
	}u;
	u.a[0] = 0x11;
	u.a[1] = 0x22;
	printf ("0x%x\n", u.i);  //0x2211 为小端  0x1122 为大端
	printf ("0x%.x\n", htons (u.i)); //大小端转换 
	return 0;
}
输出结果:
0x2211
0x1122
  • 问题二:大小端应用场景?
    一般操作系统都是小端,而通讯协议是大端的。
    大小端是由CPU和操作系统来决定的,在操作系统中,x86和一般的OS(如windows,FreeBSD,Linux)使用的是小端模式,但比如Mac OS是大端模式。
    在网络上传输数据时,由于数据传输的两端对应不同的硬件平台,采用的存储字节顺序可能不一致。所以在TCP/IP协议规定了在网络上必须采用网络字节顺序,也就是大端模式。

解答:

参看:C语言再学习-- 大端小端详解(转)
一般都是采用 union 来判断机器的字节序。
union 型数据所占的空间等于其最大的成员所占的空间。 对 union 型的成员的存取都是相对于该联合体基地址的偏移量为 0 处开始,也就是联合体的访问不论对哪个变量的存取都是从 union 的首地址位置开始。
联合是一个在同一个存储空间里存储不同类型数据的数据类型。 这些存储区的地址都是一样的,联合里不同存储区的内存是重叠的,修改了任何一个其他的会受影响。

现在明白了,我们为什么用 union 联合来测试大小端,在联合变量 u 中, 短整型变量 i 和字符数组 a 共用同一内存位置。给 a[0]、a[1] 赋值后,i 也是从同一内存地址读值的。

四、预处理

问题:

  • 问题一:用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
    #define SENCONDS_PER_YEAR (60 * 60 * 24 * 365)UL
    #define 声明一个常量,使用计算常量表达式的值来表明一年中有多少秒,显得就更加直观了。再有这个表达式的值为无符号长整形,因此应使用符号 UL。
  • 问题二:写一个“标准”宏MIN ,这个宏输入两个参数并返回较小的一个。
    #define MIN(A,B) ((A) <= (B) ? (A) : (B))
    实现输入两个参数并返回较小的一个,应使用三目表达式。使用必须的足够多的圆括号来保证以正确的顺序进行运行和结合。
  • 问题三:#include <filename.h>和#include “filename.h”有什么区别?
    对于#include <filename.h> ,编译器从标准库路径开始搜索 filename.h
    对于#include “filename.h” ,编译器从用户的工作路径开始搜索 filename.h

解答:

参看:C语言再学习 – C 预处理器
使用#define需要注意下面几点:
1)宏的名字中不能有空格,但是在替代字符串中可以使用空格。ANSI C 允许在参数列表中使用空格。
2)用圆括号括住每个参数,并括住宏的整体定义。
3)用大写字母表示宏函数名,便于与变量区分。
4)有些编译器限制宏只能定义一行。即使你的编译器没有这个限制,也应遵守这个限制。
5)宏的一个优点是它不检查其中的变量类型,这是因为宏处理字符型字符串,而不是实际值。
6)在宏中不要使用增量或减量运算符。

五、位操作

问题:

  • 问题一:嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。

    #define BIT3 (0x1 << 3)
    static int a;
    void set_bit3 (void)
    {
    	a |= BIT3;
    }
    void clear_bit3 (void)
    {
    	a &= ~BIT3;
    }
    
  • 问题二:不用临时变量交换两个数的值

    #include <stdio.h>
    int main(int argc, char *argv[])
    {
        int a = 2, b = 6;
        a = a ^ b;
        b = b ^ a;
        a = a ^ b;
        printf("a = %d b = %d/n", a, b);
        return 0;
    }
    结果如下:
    a = 6 b = 2
    

解答:

参看:C语言再学习 – 位操作

五、编译

问题:

  • 问题一:GCC编译过程?
    整个过程中可以划分为以下的4步流程:
    (1)预处理/预编译: 主要用于包含头文件的扩展,以及执行宏替换等 //加上 -E
    (2)编译:主要用于将高级语言程序翻译成汇编语言,得到汇编语言 //加上 -S
    (3)汇编:主要用于将汇编语言翻译成机器指令,得到目标文件 //加上 -c
    (4)链接:主要用于将目标文件和标准库链接,得到可执行文件 //加上 -o
  • 问题二:交叉编译和GCC编译有什么区别?
    本地编译:在当前编译平台下编译出来的程序只能在当前平台下运行。
    交叉编译:在当前编译平台下,编译出来的程序能运行在体系结构不同的另一种目标平台上,但是编译平台本身却不能运行该程序。
    gcc编译:
    #file libmosquitto.so.1
    libmosquitto.so.1: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked,
    BuildID[sha1]=0xb05795243eded68d65abe39b12212099c849965b, not stripped
    arm-none-linux-gnueabi-gcc 交叉编译:
    #file libmosquitto.so.1
    libmosquitto.so.1: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, not stripped

解答:

参看:C语言再学习 – GCC编译过程

六、栈溢出

问题:

  • 问题一:栈溢出产生原因?
    1)局部数组变量空间太大
    2)函数出现无限递归调用或者递归层次太深
  • 问题二:栈溢出解决方法?
    1)动态内存分配
    2)增大栈空间
    3)找到使用递归的地方,并消除BUG

解答:

参看:C语言再学习 – Stack Overflow(堆栈溢出)

七、指令

问题:

  • 问题一:如何在目录下查找一个文件?
    find . -name test.c
  • 问题二:如何列出匹配的文件名?
    grep -l ‘main’ *.c
    将列出当前目录下所有以”.c”结尾且文件中包含’main’字符串的文件名。

解答:

参看:C语言再学习 – Linux下find命令用法
参看:C语言再学习 – grep 命令(转)
其他指令:
参看:UNIX再学习 – ps、top、kill 指令
参看:C语言再学习 – Xargs用法详解
参看:C语言再学习-- readelf、objdump、nm使用详解
参看:C语言再学习 – dmesg 命令
参看:C语言再学习 – Linux 中常用基本命令
参看:C语言再学习 – vim常用快捷键(转)
参看:C语言再学习 – 常用快捷键

八、数据结构及算法

问题:

  • 问题一:二分查找、冒泡排序、快速排序、单链表插入、时间复杂度

解答:

参看:数据结构与算法 – 算法

九、进程

问题:

  • 问题一:什么是进程,进程与程序的区别?

解答:

参看:UNIX再学习 – 进程环境
1、进程的概念主要有两点:
第一,进程是一个实体。 每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据集区域(data region)和堆栈(stack region)。文本区域存储处理执行的代码;数据区域存储变量和基础讷航执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
第二,进程是一个“执行中的程序”。 程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行它),它才能成为一个活动的实体,我们称其为进程。
2、程序和进程的区别:
而程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。程序可以作为一种软件资料长期存在,而进程是一定生命周期的。程序时永久的,进程是暂时的。进程更能真是地描述并发,而程序不能。进程具有创建其他进程的功能,而程序没有。同一个程序同时运行于若干个数据集合上,它将属于若干个不同的进程,也就是说同一个程序可以对应多个进程。在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单元都是进程。
3、进程的特征
动态性: 进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性: 任何进程都可以同其他进程一起并发执行。
独立性: 进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
异步性: 由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的,不可预知的速度向前推进。
结构特征: 进程有程序、数据、和进程控制块三部分组成。
多个不同的进程可以包含相同的程序: 一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
4、进程终止
有 8 种方式使进程终止,其中 5 种为正常终止,它们是:
(1)从 main 返回
(2)调用 exit
(3)调用 _exit 或 _Exit
(4)最后一个线程从其启动例程返回
(5)从最后一个线程调用 pthread_exit
异常终止有 3 种方式,它们是:
(6)调用 abort
(7)接到一个信号
(8)最后一个线程对取消请求做出响应。


转载:https://blog.csdn.net/qq_29350001/article/details/115716559
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场