《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的bit2,在以上两个操作中, 要保持其它位不变。

#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 fun(int value)
{
    int ret, i;
    
    ret = 0;
    
    if(value > 0)
    {
        while(1)
        {
            for(i = 0; i < 5; i++)
            {
                value--;
                if(value == 0)
                {
                    return ret;
                }
            }
            ret++;
        }
    }
    else if(value == 0)
    {
        ret = 0;
    }
    else
    {
        ret = -1;
    }
    return ret;
}

 

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;

 

12.下面的代码输出是什么,为什么?

void func(void)

{

unsigned int a = 6;

int b = -20;

(a+b > 6)?puts(">6"):puts("<= 6");

}

输出>6
C中运算有规定,如果整型变量间进行数据运算,只要有一个变量是无符号的,结果就按无符号数据

 

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.怎么计算结构体所占内存?

 

26.什么是野指针?如何避免?

 

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.怎么判断大端小端

方法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("Big endian.");
    else if (*p == 1)
        puts("Little endian.");
}

 

 

 

 memcpy

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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