《C语言深度解剖》20题手把手教学

目录

1.下面代码输出什么?为什么?

2.下面代码会有什么问题,为什么?

3.下面的代码i和j的值分别是什么?为什么?

4.下面代码里,假设在32位系统下,各sizeof计算的结果分别是多少?

5.下面代码的结果是多少?为什么?

6.下面的代码,哪些内容可以被改写,哪些不可以被改写

7.下面的两段代码有什么区别?什么时候需要使用代码(2)?

8.在32位的X86系统下,输出的值是多少?

9.0x01<<2+3的值为多少?为什么?

10.定义一个函数宏,求x的平方

11.下面的两段代码有什么区别?

12.写代码向内存0x12ff7c地址上存入一个整型数0x100

13.下面代码的值是多少?

14.假设p的值为0x100000,如下表达式的值分别为多少?

15.下面代码输出结果是多少?

16.下面的代码有什么问题,为什么?

17.下面代码有什么问题?为什么?

18.下面的代码输出结果是多少?

19.下面的代码有什么问题?

20.请写一个C函数,若当前系统是Big_endian,则返回0;若是Little_endian的,则返回1


整理了好多天的试题详解内容,如果有问题欢迎来评论区或者私信我,一起交流学习!

1.下面代码输出什么?为什么?

>6

会发生算术提升,以下代码int排名比unsigned int排名高,所以a+b时编译器会发生隐式转换把b提升为一个很大的正数。(无符号整形提升,高位补0)

void foo(void)
{
	unsigned int a = 6;
  //00000101 
  //00000000 00000000 00000000 00000101
	int b = -20;
 //10010100
 //00000000 00000000 00000000 00010100
	(a + b > 6) ? puts(">6") : puts("<=6");
 //00000000 0000000 00000000 00011001  25
}

算术提升:

进行运算时,操作数的类型要一致。

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。

下面的层次体系称为寻常算术转换。

long double

double

float

unsigned long int

long int

unsigned int

int

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。

2.下面代码会有什么问题,为什么?

运行到strcpy的时候会出现内存异常

因为str1数组没有结束标志符所以str1数组后面继续存储的不是'\0'而是乱码

void foo(void)
{
	char string[10], str1[10];
	int i;
	for (i = 0; i < 10; i++)
	{
		str1[i] = 'a';
	}
	strcpy(string, str1);
	printf("%s ", string);

}

关于strcpy:

  • 源字符串必须以 '\0' 结束。
  • 会将源字符串中的 '\0' 之前的内容拷贝到目标空间。
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可变。

3.下面的代码i和j的值分别是什么?为什么?

i = 10; j = 1;

原因:

i是局部变量被static修饰,出了作用域不会被销毁,只初始化一次

j是全局变量,每次都被初始化,即每次都从0开始j++

static int j;
int k = 0;

void fun1(void)
{
	static int i = 0;
	i++;
}

void fun2(void)
{
	j = 0;
	j++;
}

int main()
{
	for (k = 0; k < 10; k++)
	{
		fun1();
		fun2();
	}
	return 0;
}

static静态

static修饰局部变量改变了变量的生命周期

局部变量进入作用域创建,出作用域销毁

static修饰局部变量的时候,局部变量就变成了静态局部变量,出了局部范围不会被销毁,下一次进入函数依然存在因为static修饰的局部变量是存储在静态区的

让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束

static修饰全局变量

一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。

4.下面代码里,假设在32位系统下,各sizeof计算的结果分别是多少?

答案如下代码中所示

//sizeof计算的是对象所占内存大小,单位是字节

int main()
{
	int* p = NULL;

	printf("%d\n", sizeof(p));//4  相当于sizeof(int*),求指针占用几个字节
	printf("%d\n", sizeof(*p));//4  相当于sizeof(int),求整型占几个字节

	return 0;
}
int main()
{
	int a[100];

	printf("%d\n", sizeof(a));//400  4*100=400
	printf("%d\n", sizeof(a[100]));//4 计算第101个元素大小,
	printf("%d\n", sizeof(&a));//4   &a取出的是整个数组的地址,数组的地址也是地址,是地址大小就是4个字节
	printf("%d\n", sizeof(&a[0]));//4  取出的是数组第一个元素的地址

	return 0;
}
	int b[100];

	void fun(int b[100])
	{
       sizeof(b);//4  C语言的bug,数组在有些条件下会隐式转变成指针
	}

5.下面代码的结果是多少?为什么?

255

原因:

-128是unsigned char类型数据能表示的最小负数。所以a【127】往后的值就会发生栈溢出。

-129需要9位才能储存下来,而char的类型只要8位,所以往后下去会丢弃高位。

当a【255】的时候,值为-256,但-256除去高位,剩下的低8位为0。所以此时被strlen函数认为是字符串结束标志'\0'

0~254 就是255个字符个数

int main()
{
	signed char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));

	return 0;
}

strlen 库函数

求字符串长度,从给定的地址向后访问字符,统计\0之前出现的字符个数

6.下面的代码,哪些内容可以被改写,哪些不可以被改写

const int* p;//p可变,p指向的对象不能变
int const* p;//p可变,p指向的对象不可变
int* const p;//p不可变,p指向的对象可变
const int* const p;//指针p和p指向的对象都不可变

const是一个C语言的关键字

常属性(不能被修改)

p是一个变量,专门用来存放地址的,这个变量就被称为指针变量(存放在指针中的值都被当成地址处理)              地址

*p是p指向的对象 *p表示此指针指向的内存地址中存放的内容,一般是一个和指针类型一致的变量或者常量。       变量或常量

&p取出变量p的地址    地址

(*p)返回p 的值作为地址的那个空间的取值。

(&p)返回当时声明p 时开辟的地址。可以用赋值语句对内存地址赋值。

7.下面的两段代码有什么区别?什么时候需要使用代码(2)?

//1
int i = 10;
int j = i;
int k = i;


//2
volatile int i = 10;
int j = i;
int k = i;

相同点:

如果将j,k的值打印出来,两条代码都是j=10;k=10;两个代码值一样

区别:

代码1:这个时候编译器会对代码进行优化,因为接下来到两条语句中,i没有被赋值(没有被用作左值)。编译器就会认为i没有发生变化,编译器不会生出汇编代码重新从内存中取i的值,提高了效率

注意:在两条语句中i都没有被赋值

代码2:volatile是C语言中的一个关键字,它会告诉编译器i是随时可能发生变化的,每次使用都必须从内存中取出i的值。所以编译器生成的汇编代码会重新从i的地址读取数据放在k中。

代码2的使用时机:如果i是一个寄存器变量,表示一个端口数据或者是多个线程的共享数据,那么就容易出错,所以说volatile可以保证对特殊地址的稳定访问

8.在32位的X86系统下,输出的值是多少?

5,2000000

#include <stdio.h>

int main()
{
    int a[5] = {1,2,3,4,5};
    int *ptr1 = (int *)(&a+1);
    int *ptr2 = (int *)((int)a+1);//注意这里被强制类型转换成为整型int
    
    printf("%x,%x",ptr1[-1],*ptr2);//5,2000000
    return 0;
}

5

ptr1指向的是数组中5之后的元素

ptr1[-1] = *(ptr-1) 所以ptr1[-1]指向的是4之后的元素也就是5

2000000

假设是小端存储

01 00 00 00 02 00 00 00 03 00 00 00……

a(数组名)表示数组首元素的地址,a+1指向的是数组首元素中往后跳一个字节

一个整数是四个字节,a指向数组首元素第一个字节,a+1指向数组首元素第二个字节

因为被强制转换成int*,所以打印的时候要从第二个字节开始往后访问四个字节才是一个完整的整型,也就是00 00 00 02

因为是小端存储,打印转换出来也就是2000000

%x、%X、%#x、%#X 的区别

都是十六进制输出,但是小写的x输出小写字母,大写X输出大写字母(十六进制的有包含字母表示)加了#是输出标准的十六进制带有0x

9.0x01<<2+3的值为多少?为什么?

32

因为加号运算符优先级大于移位运算符,所以编译器先计算 2 + 3,再计算 0x01 << 5

00000001往左移5位就是00100000 2^5=32

10.定义一个函数宏,求x的平方

#include <stdio.h>

#define (x) SQUARE(x) ((x)*(x))

int main()
{
    int a = 4;
    int s = SQUARE(a);
    printf("%d\n",s);
    return 0;
}

但是该语句有缺陷传入++a这样的内容函数宏会失效

11.下面的两段代码有什么区别?

两代码的内存对齐不一样,一个是12一个是8

//1
struct TestSTruct1
{
    char c1;
    short s;
    char c2;
    int i;
};

//2
struct TestStruct2
{
    char c1;
    char c2;
    short s;
    int i;
};

12.写代码向内存0x12ff7c地址上存入一个整型数0x100

//1
int *p = (int *)0x12ff7c;
*p = 0x100;
//注意:将地址0x12ff7c赋值给指针变量p的时候必须强制类型转换



//2
*(int *)0x12ff7c = 0x100;
//需要存入整型就要强制转换为整型变量

13.下面代码的值是多少?

2,5

main()
{
    int a[5] = {1,2,3,4,5};
    int *ptr = (int *)(&a+1);
    printf("%d,%d",*(a+1),*(ptr-1));//2,5
}

数组名a是指数组首元素的地址,那么a+1就是往后走一个,就是数组第二个元素的地址,就是2

ptr中存的是5之后的地址,ptr-1就是往前走一个,4之后就是5

14.假设p的值为0x100000,如下表达式的值分别为多少?

p + 0x1 = 0x_______?0x100014

(unsigned long)p + 0xl = 0x________? 0x100001

(unsigned int*)p + 0xl = 0x______?0x100004

struct Test
{
    int Num;
    char *pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}*p;

十六进制由0-9,A-F组成与10进制的对应关系是:0-9对应0-9;A-F对应10-15;N进制的数可以用0~(N-1)的数表示,超过9的用字母A-F。

1. 指针

指针加1就等于指针向后跳过指针所指向类型的字节数

这里p指向的是整个结构体,结构体的内存占用20个字节         

所以p + 0x1 = 20 + 0x1 ,转换为十六进制就是0x100014

21=1*16+4

2.数值

题目给出条件p的值0x100000

(unsigned long)p是将地址 0x100000以无符号的长整型读出,+1就只是在原本值上+1即可,所以(unsigned long)p+0x1 = 0x100001

3.指针

题目给出条件p的值0x100000

unsigned int * 的内存偏移量为4,所以(unsigned int * )p+0x1 = 0x100004

因为这里的强制类型转换是int* 这里的类型是指针,+1不是单纯的加值而是是跳过一个指针,所以是+4

15.下面代码输出结果是多少?

1

#include <stdio.h>
int main(int argc,char * argv[])
{
    int a[3][2] = {(0,1),(2,3),(4,5)};
    int *p;
    p = a[0];
    printf("%d",p[0]);//1
}

a是一个二维数组,但是对它的初始化要用大括号{}进行,而这里是用括号(),而且圆括号里用的是逗号,这里按逗号表达式进行运算从左向右依次运算,整个逗号表达式的值为最右边的表达式的值

所以存放的只有{1,3,5}

存的二维数组实际是:

13

50

00

三行两列,二维数组中没写出的元素会用0来补

指针p指向的是a[0],p[0]指向的是二维数组第一个元素a[0][0],也就是1

16.下面的代码有什么问题,为什么?

void fun(char a[10])
{
    char c = a[3];
}

int main()
{
    char b[10] = "abcdefg";
    fun(b[10]);
    return 0;
}

代码问题:

1.编译器会警告,编译器需要一个char*类型的参数,但是传递过去的是一个char类型的参数,这个时候fun函数会将传入的char类型的数据当作地址处理,同样会发生错误。

解释:

这里的一维数组传参出现问题,fun(b[10])这种写法是不对的,这个相当于实参是char类型,因为实际数组传参不会把整个数组传过去,也不会创建一个新数组来接收这个内容,fun(b)就可以了,因为数组名表示数组首元素的地址,本质传的是指针,也就是char*类型

同样的,函数的返回值也不能是一个数组,而只能是指针函数本身是没有类型的,只有函数的返回值才有类型。

2.调用fun(b[10])会出现越界访问,因为数组b只有10个元素,但是却访问第11个元素

编译的时候由于没有去实际地址取值,所以不会报错,但运行的时候会计算b[10]的实际地址,并且取值。这个时候就会发生越界错误

一维数组传参

(1)、形参以数组的形式

void fun(char a[10])

void fun(char a[ ])

void fun(char a[100])

三种写法都可以,数组的大小没有意义,可以省略也可以随便写

(2)、形参以指针的形式

void fun(char* p)

17.下面代码有什么问题?为什么?

struct student
{
    char *name;
    int score;
}stu,*pstu;

int main()
{
    pstu = (struct student*)malloc(sizeof(struct student));//只为为指针变量ptsu分配了内存
    strcpy(pstu->name,"Jimy");
    pstu->score = 99;
    free(pstu);
    return 0;
}

问题:

为指针变量ptsu分配了内存,但是没有给name指针分配内存

指针都必须单独分配内存

原因:

结构体变量分配结构体本身大小的空间,结构体指针也是指针所以只分配分配4个字节,因为任何类型的指针都是分配四个字节的指针空间。

所以在定义结构体stu开辟内存的时候,只分配结构体成员char *name了四个字节的指针空间

但是name指针并没有一个合法的地址,它里面存的只是一些乱码,当使用strcpy对其访问的时候就会出错,因为没有正确的地址。

所以正确的写法应该也使用malloc给name指针一块合法空间

struct student
{
    char *name;
    int score;
}stu,*pstu;

int main()
{
    pstu = (struct student*)malloc(sizeof(struct student));//只为为指针变量ptsu分配了内存
    pstu->name = (char*)malloc(20);
    
    strcpy(pstu->name,"Jimy");
    pstu->score = 99;
    
    //释放
    free(pstu->name);
    pstu->name = NULL;
    
    free(pstu);
    pstu = NULL;//每个malloc都要对应一个free
    
    return 0;
}

在为结构体指针分配内存时是从外向里,即先分配结构体的指针再分配成员指针

释放时则反过来,从里向外,先释放成员指针再释放结构体指针。

18.下面的代码输出结果是多少?

0

1

2

5

10

void fun(int i)
{
    if(i>0)
    {
        fun(i/2);
    }
    printf("%d\n",i);
}

int main()
{
    fun(10);
    return 0;
}

递归:函数自己调用自己

先调用完之后才开始打印,当走到i=0的时候,不再进入if中的函数,直接打印出0

fun函数走的流程的正常顺序,但打印是反过来

fun函数的计算结果:10 5 2 1 0

打印的结果是:0 1 2 5 10

递归流程图:

19.下面的代码有什么问题?

char c;
c = getchar();
if(EOF == c)
{
    ...
}

getchar函数的返回类型是int,原型为:int getchar(void);

但是c却是char类型的,char的取值范围是[-128,127]

如果宏EOF的取值在char的取值范围之外,那么EOF的值将无法全部完整的保存到c中,会发生截断。将EOF的低八位保存在c里,这样if语句有可能总是失败。

getchar函数是从键盘中读取字符,返回类型是int(返回的是字符的ASCII码值)错误返回-1

20.请写一个C函数,若当前系统是Big_endian,则返回0;若是Little_endian的,则返回1

题目意思:测试当前编译器是否是大端还是小端

大端字节序存储:

指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中

11 22 33 44

低 高

小端字节序存储:

指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中

44 33 22 11

低 高

//1.指针
int main()
{
	int a = 1;
	char* p = (char*)&a;
	if (*p == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}


//2.联合体
int check_sys()
{
	union check;
 {
     int i;
     char ch;
 }c;
 c.i = 1;
 
 return (c.ch == 1);
}

猜你喜欢

转载自blog.csdn.net/Ll_R_lL/article/details/124496345