《C语言知识点》
https://wenku.baidu.com/view/5bf31284ba0d4a7302763a8a.html
1. #define定义的宏和const定义的常量有什么区别?
1、两者的区别 (1) 编译器处理方式不同 #define 宏是在预处理阶段展开。 const 常量是编译运行阶段使用。 (2) 类型和安全检查不同 #define 宏没有类型,不做任何类型检查,仅仅是展开。 const 常量有具体的类型,在编译阶段会执行类型检查。 (3) 存储方式不同 #define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。(宏定义不分配内存,变量定义分配内存。) const定义的变量属于只读变量。是有分配内存的。
注意:只读变量和常量是有区别的。常量肯定是只读的,例如5, “abc”,等,肯定是只读的,因为程序中根本没有地方存放它的值,当然也就不能够去修改它。而“只读变量”则是在内存中开辟一个地方来存放它的值,只不过这个值由编译器限定不允许被修改。
其中:int a[n]。数组的大小n应该是个常量。所以用const int n = 5。这个是不行的。
例程:
#define N 2 + 3
int a = N / 2;
a是多少?
int a = 2 + 3 / 2 因为a为int型,所以不能有小数点 所以a = 3
const char *pContent; //*pContent是const, pContent可变
const (char *) pContent;//pContent是const,*pContent可变
char* const pContent; //pContent是const,*pContent可变
const char* const pContent; //pContent和*pContent都是const
2. 给定一个整型变量a,写两段代码,第一个设置a的bit3,第二个清除a的bit3,在以上两个操作中, 要保持其它位不变。
#define BIT3 (0x1 << 3) static int a; void set_bit3(void) { a |= BIT3; } void clear_bit3(void) { a &= ~BIT3; } 最上面的括号一定要加上,不然在clear_bit3中会有问题,~的优先级问题
3. char str[20]="0123456789";
int len1=strlen(str);
int len2=sizeof(str);
len1和len2分别是什么值?
len1 = 10; strlen计算字符串长度,碰到\0就停止计数 len2 = 20,; sizeof计算的是数组内存长度。所以str[20]改为str[],那么len2 = 11;他会计算包括null这个
4. 请问int * p 和 char * p分别占几个字节?为什么?
在32位系统中,都是4个字节。
因为计算的是指针的长度,至于int和char,指的是指针所指向内存的数据类型。
5. 下面的代码使用了__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中的浮点运算是不明智的; 4、ISR中不应该使用printf()函数,printf是不可重入函数(malloc函数也不能用)。
为什么ISR不能有返回值和传参?
因为对于ISR是由硬件触发的,因此没有调用者。
为什么ISR不能用printf和malloc函数?
首先因此这两个函数都是不可重入函数。
printf --------引用全局变量stdout
malloc --------全局内存分配表
而中断是随机发生的,如果先使用printf函数,此时中断来临,最后中断里面又使用了printf,那么此时就会改变全局变量stdout的值,也就是最后输出到屏幕的值不是自己所期望的。
6.关键字static的作用是什么?
1.修饰全局变量时,改变该变量的文件作用域,在其他文件不可调用。 2.修饰局部变量时,改变该变量存储方式,该变量在全局数据区分配内存。但是变量的作用域不变。 3.修饰函数时,改变该函数的文件作用域,其他文件不可调用。
7.用+、-、++、--实现a/5,a为一整数,如a=10,则返回2,a为21,则返回4。
int divide_by_5(int a)
{
int result = 0;
// 处理正数
while (a >= 5)
{
a -= 5;
result++;
}
// 处理负数
while (a <= -5)
{
a += 5;
result--;
}
return result;
}
8.可重入函数和不可重入函数
《可重入函数和不可重入函数》 - 一个不知道干嘛的小萌新 - 博客园 (cnblogs.com)
在多线程中,尤其要特别主要这个概念。对资源要有保护机制。
9.定义一个标准宏MAX,总是输出最大值
#define MAX((a),(b)) ((a)>(b))?(a):(b) 注意在使用时,尽量不要传入自增++或自减—表达式,否则将会导致多次运算
10.关键字extern、static、const、volatile分别是什么含义?
extern 说明变量是在其他文件中定义的,为了达到不包含头文件也可以使用其他文件中的变量 或者 extern “C” 来完成C 和 C++的相互引用 static 改变变量或函数的作用域或生存周期,当修饰全局变量和函数时,改变其作用域为仅在本文件中,其他文件不可以适用;当修饰局部变量时,扩展其生存周期至本程序结束,并且只被初始化一次。 const 改变变量的读写权限至只读 volatile 告诉编译器该变量随时有可能会变化,不要试图在编译的过程中对齐优化;同时在程序运行的过程中,每一次的访问都应该实际的从主存或寄存器中去读。一般用在多线程或中断处理程序中。
11.要求设置一绝对地址为0x67a9的整型变量的值为0xaa66
访问一绝对地址把一个整型数强制转换成一个指针。 int *ptr; ptr =(int *)0x67a9; *ptr= 0xaa66;
不能直接int (*0x67a9) = 0xaa66; 因为 0x67a9是一个字面量地址,不能直接用作指针变量的名称。
12.下面的代码输出是什么,为什么?
void func(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6)?puts(">6"):puts("<= 6");
}
输出>6
根据C语言的规则,当一个有符号整数和一个无符号整数进行运算时,有符号整数会被隐式转换为无符号整数。这意味着b会被转换为一个无符号整数。
int和unsigned int具有相同的位数(例如,32位)。当b被转换为无符号整数时,-20的二进制表示(补码形式)会被解释为一个很大的无符号整数。具体来说,-20在32位系统中的补码表示为0xFFFFFFEC,当解释为无符号整数时,其值为4294967276。
13.以下两行代码那个实现方式更好?为什么?
#define dPS struct s *
typedef struct s * tPS;
第二种。 #define 只是简单的在预处理阶段将字符串进行展开 而typedef则是定义了一个别名 举例: dPS c,d; 实际展开后成为 int *c,d 表示定义了一个整形指针c 和 整形变量 d tPS c,d 而 tPS则是一个整体,他代表的就是int *这个类型,所以定义的是两个整形指针c和d
14.写一个中断服务需要注意哪些?如果中断产生之后要做比较多的事情你是怎么做的?
1. 中断服务内的执行过程尽量短,并且不去操作硬件资源 2. 中断服务函数没有传参和返回值 3. 中断服务函数内的程序应该是可重入的,不能使用printf 4. 中断服务函数内不应做浮点运算这一类的复杂运算。 5. 中断服务函数内尽量不要使用锁(互斥锁和自选锁) 当需要做较多的事情时: 可以中断的发生和事情的处理拆分开来,中断服务程序内只做状态的修改,然后从其他程序中去对相应的状态做不同的处理。 使用 消息 或 volatile 声明的变量来做状态的传递
15.堆和栈的区别?
1.申请方式、栈的空间由操作系统自动分配以及释放。堆上的空间需要手动分配和释放(malloc以及amalloc等的区别?) 2.申请大小。堆的可用空间比较大,栈的可用空间比较小,一般是2M。 3.申请效率。栈的申请速度比较慢,堆的申请速度比较快。
16.
17.死循环的几种方式
1. while(1) { ; } 2. for(; ;) { ; } 3 LOOP: ...... goto LOOP; 4. do { ; }while(1);
18.左值和右值
左值可写,右值可读。通常,左值可以作为右值,但是右值不一定是左值。
左值可能是变量,变量又可以作为右值。但是右值可能是常量,常量不能作为左值。
19.数组名和指针的区别?
1.指针是一个变量,而数组名不是。数组名是数组的首地址,即它本身就是一个地址。 2.假设a是一个数组名,而p是一个指针,当你使用 a 和 &a 时,得到值是一样的,都是数组的起始地址。而使用 p 和 &p 时,得到的值是不一样的, p 表示指针 p 所指向的地址,而 &p 表示 p 这个变量的地址。 3.对数组的引用,如a[i],或*(a+1),需要访存一次;而对指针的引用,如*(p+1),需要访存两次。 如果理解了第二条的解释,这个应该就不难理解。因为a被认为是常数,所以取*(a+1)的值只需将a所表示的常数加1,然后从得到的地址里访存取一次即可。而对于指针,需要先从&p这个地址里把p的值取出来,然后加1,再从得到的地址里访存取一次,一共需要两次访存。
20.指针函数,函数的参数为int,返回值为字符指针
char *((*p)(int))
扩展:函数指针多用于回调函数以及逻辑层分离分层。指针函数多用于动态分配内存并返回其指针、返回数组或结构体的指针、通过返回指针避免大对象复制的开销。
21.typedef和define有什么区别
typedef定义指针的别名时,别名可以连续定义两个指针变量。define定义指针的别名是,使用这个别名连续定义两个指针变量会报错。
22.
23.不能用sizeof函数,如何判断操作系统是16位还是32位?
方法1: 16位系统: int i = 65536; cout << i; // 输出0;//装不下,最高位溢出,剩下16位的当然是0; int i = 65535; cout << i; // 输出-1;//-1的补码是65535 32位系统: int i = 65536; cout << i; // 输出65536; int i = 65535; cout << i; // 输出65535; 方法2: int a = ~0;//按位取反运算,结果为(11111111111111111111111111111111) if( a>65536 ) { cout<<"32 bit"<<endl; } else { cout<<"16 bit"<<endl; }
24.什么是4字节对齐?为什么需要对齐?
字节对齐是为了提高存取效率,并且一般是偶个字节对齐(2 4 6 8……),因为总线的位数都是偶数的(8 32 64……),并且每个周期的周期都是从偶地址开始访问的,若不是没有偶字节对齐,在某些情况(比如若某变量的内存空间为0x33 ~ 0x36)下访问一块内存将会多耗费一个周期。
4字节对齐就是32位。也就是常规的32位总线。对应我CPU每次去访问内存最大可以一次性访问32位。
比如定义一个char a[1] = 'a';这个正常只需要8位的存储,如果4字节对齐,就变成32位存储。
这个时候printf("%c",a[0]);和printf("%d",a[0]);就会出现前者是取了a[0]的前8位,后者是取了a[0]的32位。
25.怎么计算结构体所占内存?
struct s{ double a; int b; char c; } 16个字节 struct s{ char a; int b[4]; } 20个字节 struct s_1{ char a; //4 int b; //4 char c; //4 } struct s_2{ char d; //4 struct s_1 s; //12 int e; //4 } 20个字节
26.什么是野指针?如何避免?
野指针(Dangling Pointer)是指向已经被释放(或未初始化)的内存地址的指针。使用野指针会导致未定义行为(Undefined Behavior),可能引发程序崩溃、数据损坏或安全漏洞。
1.未初始化的指针: int *p; // 未初始化 *p = 10; // 野指针:p指向未知地址 2.指针指向的内存被释放: int *p = malloc(sizeof(int)); *p = 10; free(p); // 释放内存 *p = 20; // 野指针:p指向已释放的内存 3.指针越界访问 int arr[5] = {1, 2, 3, 4, 5}; int *p = arr + 10; // 越界:p指向非法地址 *p = 100; // 野指针行为 4.返回局部变量的指针: int *func() { int x = 10; return &x; // 野指针:x在函数返回后失效 }
使用工具检测野指针:
- Valgrind(Linux):检测内存泄漏和非法内存访问。
27.sizeof和strlen的区别?
sizeof是运算符,在程序编译时就已经确定了;
strlen是函数,程序运行时才能计算。
29.Int a[5] = {1,2,3,4,5} sizeof(a) = ?
20
30.scanf和gets的有何利弊
用scanf()函数输入字符串时,默认分隔符是空格、跳格(Tab)等,
因此scanf()函数不能输入含有上述字符的字符串,这是其不足之处;
与gets()相比,其优点是它可以一次输入多个字符串,而且还可以用于输入不同类型的数据,应用面较广。
用gets()函数输入时,可以输入含空格、跳格等字符的字符串,但其不足之处在于,它只能用于输入字符串,且一次只能输入一个。
31.程序编译过程
预处理:预处理相当于根据预处理命令组装成新的C程序,不过常以i为扩展名。
编译: 将得到的i文件翻译成汇编代码.s文件。
汇编:将汇编文件翻译成机器指令,并打包成可重定位目标程序的O文件。该文件是二进制文件。
链接:将引用的其他O文件并入到我们程序所在的o文件中,处理得到最终的可执行文件。
32.怎么判断大端小端
大端:高位字节(Most Significant Byte, MSB)存储在低地址,低位字节(Least Significant Byte, LSB)存储在高地址。
小端:低位字节(LSB)存储在低地址,高位字节(MSB)存储在高地址。
方法1:利用联合体
关键点:联合体的存放顺序是所有成员都从低地址开始存放。
void check_cup(void) { union{ short s; char c[sizeof(short)]; }un; un.s = 0x0102; if (un.c[0] == 1 && un.c[0] == 2) puts("Big endian."); else if (un.c[0] == 2 && un.c[0] == 1) puts("Little endian."); else puts("Unkown"); }
方法2:利用强制类型转换
void check_cup(void) { int a = 0x0102; char *p = (char *)&a; if (*p == 2) puts("Little endian."); else if (*p == 1) puts("big endian."); }
33.回调函数的优缺点
优点:解耦、灵活性高(可以根据需要动态传入不同的函数,实现“插件式”或事件驱动的设计)
缺点:类型安全性差(函数指针原型必须完全匹配,否则容易出错或导致崩溃)、难以维护(函数间关系隐藏在指针调用中,不易追踪,调试复杂)
34.怎么防止内存碎片化
内存碎片化:当程序不断分配(malloc/new)与释放(free/delete)不同大小的内存块时,内存空间就会出现很多不连续的小空洞,这些空洞单独都不够大,但合起来又占了很大内存。
结果:系统还有很多可用内存,但申请大块内存时失败(例如 malloc 返回 NULL)。
主要原因:动态分配/释放频繁大小不规律的内存。
解决方式:
1.内存池
2.尽量“等大小分配”
3.避免频繁malloc/free,尽量在初始化阶段一次性分配好。
浙公网安备 33010602011771号