《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会被转换为一个无符号整数。

  intunsigned 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,尽量在初始化阶段一次性分配好。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2020-12-14 15:22  一个不知道干嘛的小萌新  阅读(500)  评论(0)    收藏  举报