-
static的作用是什么?
static的最主要功能是隐藏(不同文件里同名函数同名变量不会冲突)其次因为static变量存放在静态存储区,所以它具备持久性(只进行一次初始化)和默认值0(字符数组当字符串用时后面不必加/0)。
1.static修饰局部变量时,改变了变量的生命周期,让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束,作用域仍为局部作用域。
2.static修饰全局变量时,这个全局变量只能在本源文件内使用,不能在其他源文件内使用。
3.static修饰函数时,该函数只在本文件内被调用或访问,不能跨文件访问,其他文件中可以定义相同名字的函数,不会发生冲突。 -
关键字const的作用?
const意味着"只读"(不是完全的答案)。
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
本质:const在谁前面谁就不可修改,const在最前面则将其后移一位即可,二者等效。
前两个的作用是一样,a是一个常整型数。
第三个意味着a是一个指向常整型数的指针(也就是指向的整型数是不可修改的,但指针可以,此最常见于函数的参数,当你只引用传进来指针所指向的值时应该加上const修饰符,程序中修改编译就不通过,可以减少程序的bug)eg:用一个指向常变量的指针作为函数的形参,可以防止该函数修改实参的值。
第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。
-
关键字volatile
随时可变的意思。直接从变量地址中读取数据,防止编译器优化,直接从寄存器中读数据。用在多线程共用、或者中断中修改的变量。 -
字节对齐理解
字节对齐能提升CPU访问数据的效率和节省内存使用空间。对于32位的内存,CPU访问内存是按照字、双字、字节访问的,如果是不对齐的字在内存中需要CPU读取两个周期。如果数据的地址是自然对齐的话,只需要一个时钟周期。
对于结构体,例如:
struct stu{
char sex;
int length;
char name[10];
};
struct stu my_stu;
这里整个结构体sizeof(my_stu)的大小为20个字节,因为sex占用一个字节,编译器默认四字节对齐,后面会空出三个字节,length占用四个字节,name数组咱用十个字节,编译器默认补齐两个字节。
所以写结构体时把结构中的变量按照类型大小从小到大声明,尽量减少中间的填补空间。也可以用#pragma pack()伪指令限定字节对齐数。
代码需要适配不同CPU时要按一字节对齐,避免不同编译器默认对齐标准不同,从而编译出的代码也不一样。 -
sizeof 与strlen
sizeof()是用来求取 “变量” 或者 “类型” 所占内存空间的大小(单位:字节)。但值得注意的是,sizeof()其实是一个运算符,与加、减、乘、除是属于一类的。
strlen()是一个库函数是专门用来计算 “字符串” 长度的,在对其进行调用前是需要包含头文件<string.h>。
当一个字符数组名作为参数传入二者,从而计算字符串的长度时,sizeof会记\0为一个字节,strlen不会。 -
数组a里 &a+1和&a[0]+1区别
&a是整个数组的首地址,&a[0]是数组首个元素的地址,二者所指的地址是一样的。
而&a+1是跳过整个数组,&a[0]+1是指下一个元素的地址即a[1]。
另外,a+1是在数组内进行移动,每次移动大小是元素类型的大小,数组名a和数组a[0]是等价的。而&a+1每次移动大小是整个数组的大小,也就是sizeof(a)。(ps:sizeof(&a)指的是&a这个地址的长度)
总结:若是对数组名进行取地址(&)操作,每次移动就是整个数组的大小,移动后指向数组末尾地址;若不对数组名取地址那么移动就是sizeof(类型)。
&a+1类型是int()[]; a的类型是int类型。
参考:https://zhuanlan.zhihu.com/p/108885036 -
什么是大小端?如何确定大小端?
小端:低位存放在低地址
大端:低位存放在高地址
// 共用体中很重要的一点:a和b都是从u1的低地址开始存放的。
union myunion
{
int a;
char b;
};
// 如果是小端模式则返回1,大端模式则返回0
int is_little_endian(void)
{
union myunion u1;
u1.a = 0x12345678;
// 地址0的那个字节内是最低位0x78(小端)或者0x12高位(大端)
if(0x78 == u1.b)
return 1;
else if(0x12 == u1.b)
return 0;
}
int is_little_endian2(void)
{
int a = 0x12345678;
char b = *((char *)(&a)); // 指针方式其实就是共用体的本质
if(0x78 == b)
return 1;
else if(0x12 == b)
return 0;
}
-
用预处理指令#define 声明常数时需注意事项
1年中有多少秒
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
意识到这个表达式将使一个16位的整型数溢出,因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
UL(表示无符号长整型) -
嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
说明:这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。典型的类似代码如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55; -
中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf(” Area = %f”, area);
return area;
}
这个函数有太多的错误了:
1). ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
2). ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
3). 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
4). 与第三点一脉相承,printf()经常有重入和性能上的问题。
-
简述strcpy sprintf与mencpy的区别
三者主要有以下不同之处:
(1)操作对象不同,strcpy的两个操作对象均为字符串,sprintf的操作源对象可以是多种数据类型,目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
(2)执行效率不同,memcpy最高,strcpy次之,sprintf的效率最低。
(3)实现功能不同,strcpy主要实现字符串变量间的拷贝,sprintf主要实现其他数据类型格式到字符串的转化,memcpy主要是内存块间的拷贝。
说明:strcpy、sprintf与memcpy都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来选择合适的函数实现拷贝功能。 -
链表与数组的区别
数组和链表有以下几点不同: (1)存储形式:数组是一块连续的空间,声明时就要确定长度。链表是一块可不连续的动态空间,长度可变,每个结点要保存相邻结点指针。 (2)数据查找:数组的线性查找速度快,查找操作直接使用偏移地址。链表需要按顺序检索结点,效率低。 (3)数据插入或删除:链表可以快速插入和删除结点,而数组则可能需要大量数据移动。 (4)越界问题:链表不存在越界问题,数组有越界问题。 说明:在选择数组或链表数据结构时,一定要根据实际需要进行选择。数组便于查询,链表便于插入删除。数组节省空间但是长度固定,链表虽然变长但是占了更多的存储空间。