C语言疑难点汇总解析

C语言疑难点汇总解析,第1张

1      大小端知识点 1.1       大小端定义

1) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

2) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

速记技巧:小弟弟(小端的低位放低地址),

举一个例子,比如数字0x12 34 56 78在内存中的表示形式为:

1)大端模式:

低地址 -----------------> 高地址

0x12  |  0x34  |  0x56  |  0x78

2)小端模式:

低地址 ------------------> 高地址

0x78  |  0x56  |  0x34  |  0x12

详解大端模式和小端模式_求佛_ce123的博客-CSDN博客_大端小端

1.2      常见CPU字节序

Big Endian : PowerPC、IBM、Sun,51

Little Endian : x86、DEC

ARM既可以工作在大端模式,也可以工作在小端模式。

网络协议使用的都是大端字节序,在Socket编程中,为了提高代码的兼容性需要使用如下宏定义进行字节序的转换

#define ntohs(n)     //16位数据类型网络字节顺序到主机字节顺序的转换

#define htons(n)     //16位数据类型主机字节顺序到网络字节顺序的转换

#define ntohl(n)      //32位数据类型网络字节顺序到主机字节顺序的转换

#define htonl(n)      //32位数据类型主机字节顺序到网络字节顺序的转换

1.3      真题再现

1)【新员工编程基础测试】

对于LittleEndian字节序的主机,在执行完Bfunc函数后全局变量a的值是( )

_UL a = 0xffffffff;

_UC Afunc( )

{

    return 0x12345678;

}

_UL Bfunc( )

{

    a = (_UL)Afunc ( );

}

A  0x12                  B  0x78

C  0x12345678            D  0xffffff78

选B

Afunc返回_UC,会发生截断,无论是大端还是小端当发生截断的时候都只会把最低位的值赋值给新变量。这里是从int截断为char,因此最终将最低位的0x78赋值给a。

2)【新员工编程基础测试】对于BigEndian字节序的主机,在执行完Bfunc函数后全局变量a的值是( )

_UL a = 0xffffffff;

_UC Afunc( )

{

    return 0x12345678;

}

_UL Bfunc( )

{

    a = (_UL)Afunc ( );

}

A  0x12                  B  0x78

C  0x12345678            D  0xffffff78

选B

Afunc返回_UC,会发生截断,无论是大端还是小端当发生截断的时候都只会把最低位的值赋值给新变量。这里是从int截断为char,因此最终将最低位的0x78赋值给a。

备注:这道题原始答案选择的是A,根据大小端来截断,我在选择这道题目的时候也觉得答案是对的,感谢热心的网友宁宁提醒了我,这道题原答案是错误的。我选择了mips的系统(mips是大端结构),录入原始代码,反汇编的结果为:

反汇编中标红的地方直接返回的值是120(0x78),

在X86的系统中,同样的代码,反汇编的Afunc也是返回的0x78

因此如果只是直接赋值,截断的情况与大小端是没有关系的。

但如下代码就和大小端有关系了:

在大端环境下,value2的值应该是0x12,而小端环境下则是0x78。这里是通过地址赋值的方式,就涉及到大小端内存的排布。

参考资料:3. 结构体位域大小端总结_掌柜Yang的博客-CSDN博客_位域结构体 大小端

2      Inline内联函数的知识点 2.1      基础知识介绍

inline函数的总结_WhiteJunior的博客-CSDN博客_inline函数

我们知道函数调用的过程会进行压栈出栈 *** 作,在这个过程中会产生额外的消耗,inline内联函数的引入就是解决一些频繁调用的小函数的大量栈空间的问题,使用inline修饰符,这是C99的新增特性。

2.2      内联函数和宏函数的区别

相同点:

内联函数的调用和宏函数有点类似,他在调用点会将代码展开,而不是开辟函数栈

不同点:

1、  编译器会对内联函数的参数类型做安全检查或自动类型转换,而宏定义则不会,他只是进行单纯的文字替换,不会做任何类型的检查
2、内联函数在运行时可调试,而宏定义不可以。
3、内联函数可以访问类的成员变量,宏定义则不能
4、在类中声明同时定义的成员函数,自动转化为内联函数。

2.3      内联函数的使用限制

1  inline只适合函数体内部代码简单的函数使用,不能包含复杂的结构控制语句例如while、switch,并且不能内联函数本身不能是直接递归函数(即,自己内部还调用自己的函数)。

2  inline函数仅仅是一个对编译器的建议,所以最后能否真正内联,看编译器的意思:它如果认为函数不复杂,能在调用点展开,就会真正内联,并不是说声明了内联就会内联,声明内联只是一个建议而已。

3 inline函数的定义放在头文件中,因为内联函数要在调用点展开,所以编译器必须随处可见内联函数的定义,要不然就成了非内联函数的调用了。所以,这要求:

·         每个调用了内联函数的文件都出现了该内联函数的定义。

4 声明跟定义要一致:

·         如果在每个文件里都实现一次该内联函数的话,那么,最好保证每个定义都是一样的,否则,将会引起未定义的行为。如果不是每个文件里的定义都一样,那么,编译器展开的是哪一个,那要看具体的编译器而定。所以,最好将内联函数定义放在头文件中。

5 内联函数的定义必须出现在内联函数第一次被调用之前

6 内联函数是以空间换时间的做法,省去调用函数的额外开销。所以代码很长或者有循环/递归的函数不适宜使用内联。

7 inline 是一种“用于实现的关键字”。关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。如下风格的函数Foo不能成为内联函数:

inline void Foo(int x, int y); // inline 仅与函数声明放在一起,不可以!

void Foo(int x, int y){}

而如下风格的函数Foo则可以成为内联函数:

void Foo(int x, int y);

inline void Foo(int x, int y) {} // inline 与函数定义体放在一起

因此,inline 是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。

虽然说内联函数可以提高执行效率,但是不可以将所有的函数都定义为内联函数。内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。

如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

3      数据类型转换 3.1      符号位扩展

https://blog.csdn.net/zhangzhi123456789/article/details/49589137

3.1.1        符号位扩展基础知识

要扩展量为有符号量,不管最终要扩展成有符号还是无符号,都遵循符号扩展;

要扩展量为无符号,不管最终要扩展成有符号还是无符号,都遵循零扩展。

样例1:

int main()

{

    char a = 0xff;         // a为-1,其为有符号量,二进制为11111111

    unsigned short b = a;  //此处a要进行符号扩展,遵循符号扩展,b的二进制为11111111 11111111

    short c = a;           //此处a要进行符号扩展,遵循符号扩展,c的二进制为11111111 11111111

    printf("c = %d\n", a);

    printf("b = %u\n", b);

    printf("c = %d", c);

    return 0;

}

运行结果:

样例2:

int main()

{

    unsigned char a = 0xff;  // a为:255,其为无符号量,二进制为11111111

    unsigned short b = a;    //此处a要进行符号扩展,遵循零扩展,b的二进制为00000000 11111111

    short c = a;             //此处a要进行符号扩展,遵循符号扩展,c的二进制为00000000 11111111

    printf("c = %d\n", a);

    printf("b = %u\n", b);

    printf("c = %d", c);

    return 0;

}

运行结果:

从上面的结果可以看到符号位的扩展方法只和待扩展变量的符号有关,如果待扩展的变量是无符号变量,则遵循零扩展,否则就是符号位扩展。

3.1.2        类型转换和扩展

1)、短数据类型扩展为长数据类型

i.要扩展的短数据类型为有符号数的

  进行符号扩展,即短数据类型的符号位填充到长数据类型的高字节位(即比短数据类型多出的那一部分),保证扩展后的数值大小不变

1:char x=10001001b;   short y=x;  则y的值应为11111111 10001001b;

2:charx=00001001b;   short y=x;  则y的值应为00000000 00001001b;

ii. 要扩展的短数据类型为无符号数的

  进行零扩展,即用零来填充长数据类型的高字节位

 1:unsignedchar x=10001001b;   short y=x;  则y的值应为00000000 10001001b;

 2:unsignedchar x=00001001b;   short y=x;  则y的值应为00000000 00001001b;

2)、长数据类型缩减为短数据类型

如果长数据类型的高字节全为1或全为0,则会直接截取低字节赋给短数据类型;如果长数据类型的高字节不全为1或不全为0,则转会就会发生错误(这句话来源于网上,暂时还没有找到真实的环境验证在高字节不全为1或者0的情况下,转换会发生错误的场景,因此待我验证之后在这里补充验证结果,也欢迎大家一起讨论这句话是否正确)。

3)、同一长度的数据类型中有符号数与无符号数的相互转化

直接将内存中的数据赋给要转化的类型,数值大小则会发生变化。另短类型扩展为长类型时,但短类型与长类型分属有符号数与无符号数时,则先按规则一进行类型的扩展,再按本规则直接将内存中的数值原封不动的赋给对方

3.2      有符号和无符号数据的比较

https://blog.csdn.net/u010765526/article/details/73613815

在有符号和无符号数据类型的比较中,自动将有符号整形转换为无符号整形,之后按照正数的方式进行比较。例子:

int main()

{

    int a = -1;

    unsigned int b = 1;

    if (a > b) {

        printf("a > b, a = %d, b = %u\n", a, b);

    } else {

        printf("a <= b, a = %d, b = %u\n", a, b);

    }

    if (a > (int)b) {

        printf("a > (int)b, a = %d, b = %u\n", a, b);

    } else {

        printf("a <= (int)b, a = %d, b = %u\n", a, b);

    }

    return 0;

}

测试结果: 

该代码中,第一个比较语句使用a和b进行比较,根据有符号数和无符号数的比较原则,编译器在进行比较的时候首先将a转换为无符号数(超大数)然后按照正数和b进行比较,自然会出现a>b。在第二个比较语句中,对b进行强转,这样都是有符号数进行比较,a为-1,b为1,自然认为a<=b。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/706937.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-24
下一篇 2022-04-24

发表评论

登录后才能评论

评论列表(0条)

保存