博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C语言深度剖析总结(一)
阅读量:2444 次
发布时间:2019-05-10

本文共 5989 字,大约阅读时间需要 19 分钟。

 

 

1.register关键字请求编译器尽可能的将变量存储在CPU内部寄存器中而不是内存中以提高访问效率。

数据从内存中取出来首先放到寄存器中, 然后CPU再从寄存器中读取数据来进行处理, 处理完后同样通过寄存器放到内存中, CPU不直接和内存打交道。

Register的值必须是一个单个的值, 并且其长度应小于或等于int的长度, 而且register变量可能不放在内存中, 所以不能用取地址符(&)来获取register变量的地址。

 

2.

 

输出结果为:255

 

先来了解下补码的知识:

   在计算机中, 数值一律用补码来表示(存储), 主要原因是使用补码, 可以将符号位和其它位统一处理, 同时, 减法也可以按加法来处理, 两外, 两个用补码表示的数相加时, 如果最高位(符号位)有进位, 则进位被舍去。整数的补码和其原码一致, 负数的补码:符号位为1 其余位为该数绝对值的原码按位取反, 然后整个数加1.

   于是, for()循环中, i = 255时, a[255] = -256, 由于char类型的只能表示-128~127之间的数, 所以-256已经发生了溢出, 只将最低8位赋给a[255], -256的补码的低8位为0 所以a[255] = 0, 也就是字符串的结尾符’/0’ascii值, 当用strlen()计算a的长度时, 遇到a[255]时停止, 于是strlen(a) = 255;   (a[0]~a[254])

 

3. 下面输出:

 

输出结果:

11111111111111111111111111110110

fffffff6

-10

 

分析:当表达式中既有signed unsigned时, 计算结果为unsigned类型的, 所以i + j的结果也是unsigned类型的,i + j = -10, -10的补码可以按照上面说的计算补码的介绍得出

   如果把i + j的结果复制给int类型的, -10的补码会被解析成有符号类型的, res的输出为-10;

 

4.

 

程序将无限的输出####, 因为iunsigned类型的, 永远>=0, 所以上面的for()是个死循环

 

5.如果函数没有返回值, 那么声明为void 类型。在C语言中, 如果不加返回值类型限定, 那么编译器将函数返回值默认为int类型, 不是void

  如果函数无参数, 那么声明其参数为void

6. 如果函数的参数可以是任意类型的指针, 那么声明其参数为void*类型, 想标准库中的memcpy(), memset()函数, 他们的指针参数的类型和返回值类型都是void*类型的, 这也真实体现了内存操作函数的意义, 因为它操作的对象仅仅是一片内存, 而不论这篇内存的类型是什么。

 

7. void不能代表一个真实的变量, 因为定义变量时必须分配内存空间, 定义void类型变量, 编译器到底应该为变量分配多大的空间呢? 所以不行

void a; // error

funvoid a);// error

 

8. 编译器通常不为普通的const只读变量分配存储空间, 而是将他们保存在符号表中, 这使得他成为一个编译期间的值, 没有了读写内存的操作, 使得他的效率也很高。

例如:

#define M 3  

const int N = 5;    //此时并未将N放入内存中, 只是保存在符号表中

 

int i = N;   //此时为N分配内存, 以后不再分配

int j = N;   //此时不为N分配内存

 

int k = M;   //预编译期间进行宏替换, 分配内存

int l = M;    //再次进行宏替换, 分配内存

 

    const定义的只读变量从汇编的角度来看, 只是给出了对应的内存地址, 而不是向#define一样给出的是立即数, 所以, const定义的只读变量在程序运行过程中只有一份拷贝(因为他是全局的只读变量, 放在静态存储区), #define定义的宏常量在内存中有若干份拷贝。

#define宏是在预编译阶段进行替换, const修饰的只读变量是在编译的时候确定其值。

#define宏没有类型, const修饰的只读变量具有特定的类型, 所以在编译的时候还能够进行类型检查。

 

9. volatile 关键字和const 一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器

未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编

译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

先看看下面的例子:

int i=10;

int j = i//(1)语句

int k = i//(2)语句

   这时候编译器对代码进行优化,因为在(1)、(2)两条语句中,i 没有被用作左值。这时候编译器认为i 的值没有发生改变,所以在(1)语句时从内存中取出i 的值赋给j 之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给k 赋值。编译器不会生成出汇编代码重新从内存里取i 的值,这样提高了效率。但要注意:(1)、(2)语句之间i 没有被用作左值才行。

再看另一个例子:

volatile int i=10;

int j = i//(3)语句

int k = i//(4)语句

volatile 关键字告诉编译器i 是随时可能发生变化的,每次使用它的时候必须从内存中取出i

的值,因而编译器生成的汇编代码会重新从i 的地址处读取数据放在k 中。

这样看来,如果i 是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数

据,就容易出错,所以说volatile 可以保证对特殊地址的稳定访问。

 

10.const volatile int i = 10;

   这个表示i是一个const常量, 不能够被用户代码改变, 但是i可以被其他因素改变。

 

下面是找到的一些分析:

   const表示我们自己的代码不会改变这个值(别的代码或者硬件有可能改变这个值)。volatile表示禁止优化。因为编译器会认为如果代码没有改变变量,那么这个变量就不会改变,因此编译器会用寄存器把该变量缓存起来,每次需要读取变量值的时候,就从缓存中读取。这在大多数时候是正确的,但是在多线程或者中断的场合就不正确了。

一个值可以同时是constvolatile。例如,硬件时钟一般设定为不能由程序改变,这一点使他成为const; 但它被程序以外的代理改变,这使它成为volatile的。只需在声明中同时使用这两个限定词。

volatile标识一个变量意味着这个变量可能被非本程序的其他过程改变,例如某个访问这一变量的某中断程序。

 

CSDN讨论链接:

 

11.柔性数组:

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,但结

构中的柔性数组成员前面必须至少一个其他成员。柔性数组成员允许结构中包含一个大小可

变的数组。sizeof 返回的这种结构大小不包括柔性数组的内存。包含柔性数组成员的结构用

malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组

的预期大小。

柔性数组到底如何使用呢?看下面例子:

typedef struct st_type

{

int i;

int a[0];

}type_a;

有些编译器会报错无法编译可以改成:

typedef struct st_type

{

int i;

int a[];

}type_a;

这样我们就可以定义一个可变长的结构体, 用sizeof(type_a) 得到的只有4 , 就是

sizeof(i)=sizeof(int)。那个0 个元素的数组没有占用空间,而后我们可以进行变长操作了。通

过如下表达式给结构体分配内存:

type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));

这样我们为结构体指针p 分配了一块内存。用p->item[n]就能简单地访问可变长元素。

但是这时候我们再用sizeof*p)测试结构体的大小,发现仍然为4。是不是很诡异?我们

不是给这个数组分配了空间么?

别急,先回忆一下我们前面讲过的“模子”。在定义这个结构体的时候,模子的大小就

已经确定不包含柔性数组的内存大小。柔性数组只是编外人员,不占结构体的编制。只是说

在使用柔性数组时需要把它当作结构体的一个成员,仅此而已。再说白点,柔性数组其实与

结构体没什么关系,只是“挂羊头卖狗肉”而已,算不得结构体的正式成员。

 

12. Big EndianLittle Endian:

Big Endian(大端模式):字数据的高字节存储在低地址中, 而字数据的低地址则存放在高地址中。

Little Endian(小端模式):字数据的高字节存储在高地址中, 而字数据的低地址存放在低地址中。

 

判断系统存储模式:

 

 

大小端转换:

 

输出:0x78563412

 

13. 

 

输出结果: 4

enum类型的大小是size_t, size_t一般是整形的长度

 

14.

 

 

这段代码中, 定义了两个TEST类型的指针,const PTRTEST ptr1PTRTEST const ptr2 这两种形式都是定义了一个指针常量, 就如同const int Iint const I都是定义个一个常量I一样。 对于编译器来说, 只认为PTRTEST 是一个类型, 并不去管他不是不指针类型。

 

 源自:语言深度解剖》(陈正冲编著)

 

 

转载地址:http://dbkqb.baihongyu.com/

你可能感兴趣的文章
Windows 98 桌面主题和用户管理(转)
查看>>
Windows 98 注册表妙用(转)
查看>>
自行添加欢迎对话框中的文本(转)
查看>>
Win2K Terminal Service使用经验(转)
查看>>
Windows 98 注册表应用的30个实例(转)
查看>>
为 Windows 98 的注册表数据库减肥(转)
查看>>
同时最小化多个Windows窗口(转)
查看>>
Windows Vista 内建管理员帐号被禁用(转)
查看>>
Geforce 4 MX 440强制Vista 开启玻璃效果(转)
查看>>
Windows Vista Beta2 中文版优化归类(转)
查看>>
用SQL进行多表查询(转)
查看>>
Oracle 9i管理的用户(转)
查看>>
Oracle 9i管理工具的使用(转)
查看>>
目前主流的两类关系型数据库系统(转)
查看>>
在Oracle数据库10g中跟踪SQL(转)
查看>>
Oracle 10g Release2新功能之变化通知(转)
查看>>
Oracle 10g 新特性之虚拟专用数据库(转)
查看>>
深刻理解Oracle数据库的启动和关闭(转)
查看>>
将Oracle 10g内置的安全特性用于PHP(转)
查看>>
骇客攻击:跳板攻击与防御(1)(转)
查看>>