正数的源码反码补码都是一样的。
负数的补码是,符号位不变,其余位取反加1。
计算机存储负数,存储的是补码,这样方便计算机的计算。
需要注意的是,在进行移位运算时,有符号和无符号是不同的
有符号,右移,对于正数补0,负数补1
无符号时都是正数,所以无论左移还是右移都是补0的。
15 unsigned int a = 0x80000000;
16 printf("a:%x, a >> 31:%x, a >> 31:%d\n", a, a >> 31, a >> 31);
17
18 int b = 0x80000000;
19 printf("b:%x, b >> 31:%x, b >> 31:%d\n", b, b >> 31, b >> 31);
20
21 int c = 0x40000000;
22 printf("c:%x, c << 1:%x, c << 1:%d\n", c, c << 1, c << 1);
23
24 int d = 0xF0000000;
25 printf("d:%x, d << 1:%x, d << 1:%d\n", d, d << 1, d << 1);
结果:
a:80000000, a >> 31:1, a >> 31:1
b:80000000, b >> 31:ffffffff, b >> 31:-1
c:40000000, c << 1:80000000, c << 1:-2147483648
d:f0000000, d << 1:e0000000, d << 1:-536870912
应用一下:
写一个小函数,打印出传入数据的二进制。
8 void main(int argc, char* argv[])
9 {
10 if (argc < 2) {
11 printf("the num of param is wrong:%d\n", argc);
12 }
13
14 int param = atoi(argv[1]);
15 printf("source code %d :\n", param);
16
17 int bit = 0;
18 for (int cnt = 31; cnt > -1; cnt--) {
19 bit = (unsigned int)(param & (0x1 << cnt)) >> cnt;
20 printf("%d", bit);
21 if (0 == (cnt) % 4) {
22 printf(" ");
23 }
24 }
25 printf("\n");
26 }
输出结果:
source code -10 :
1111 1111 1111 1111 1111 1111 1111 0110
如果将第19行换成
bit = (param & (0x1 << cnt)) >> cnt;
则输出的结果将会是:
source code -10 :
-1111 1111 1111 1111 1111 1111 1111 0110
这种情况就是有符号的数被移位到最高位,变成负数,右移的时候会被补1,导致输出的第一个数是-1.因此有符号的数,进行移位操作的时候还是需要小心的。
sizeof 是一个关键字,并不是一个函数,计算的是变量的大小
strlen计算的是字符串地址,一直到\0为止的,字符串的大小
sizeof 在编译的时候就已经确定了计算的结果,而strlen是在程序跑起来之后,才会得出计算结果。
以上是原理,sizeof之所以是关键字,是因为它不需要包含其他头文件,并且后面可以不用打括号
64位系统下判断,以下代码输出结果:
4 void fun1(char *buffer)
5 {
6 printf("fun sizeof a:%d\n", sizeof(buffer));
7 printf("fun strlen a:%d\n", strlen(buffer));
8 }
9
10 int main()
11 {
12 char a[15] = "hello word";
13 printf("sizeof a:%d\n", sizeof(a));
14 printf("strlen a:%d\n", strlen(a));
15 printf("strlen &a:%d\n", strlen(&a));
16 fun1(a);
17 return 0;
18 }
因为c语言里面,数组传递的是数组的地址,根据之前讲的原理,sizeof的结果会变的,而strlen的结果是不变的
sizeof a:15
strlen a:10
strlen &a:10
fun sizeof a:8
fun strlen a:10
原理:指针的算数运算,只有加减,没有乘除,它的意义是,在当前地址的基础上,加上或者减去,指针指向的数据类型的基础上,加上或者减去n。所以具体输出的结果需要看,指针指向的数据类型。
12 int a[5] = {0};
13 char b[5] = {0};
14 printf("&a[3] - &a[0] :%d\n", &a[3] - &a[0]);
15 printf("&b[3] - &b[0] :%d\n", &b[3] - &b[0]);
16 printf("((int)&a[3] - (int)&a[0]) / sizeof(char) :%d\n", ((int)&a[3] - (int)&a[0]) / sizeof(char));
17 printf("((int)&b[3] - (int)&b[0]) / sizeof(char) :%d\n", ((int)&b[3] - (int)&b[0]) / sizeof(char));
根据以上原理打印结果为:
&a[3] - &a[0] :3
&b[3] - &b[0] :3
((int)&a[3] - (int)&a[0]) / sizeof(char) :12
((int)&b[3] - (int)&b[0]) / sizeof(char) :3
数组名:代表的是数组的首元素的地址,类型为数组元素的类型。
数组地址:代表的是数组的地址,类型为整个数组。
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
结果为2,5
小端:高字节放在高位,低字节放在地位
大端:高字节放在地位,高字节放在高位
12 int a = 0x12345678;
13 char b = *(char*)(&a);
14 if (0x78 == b) {
15 printf("little\n");
16 } else {
17 printf("big\n");
18 }
19 return 0;
这是一个函数,只有在debug模式下才可以使用,通常用来检测传入参数。assert判断条件为假时,会终止程序运行,并且报错。
attribute((at(地址)))绝对定位,绝对定位到flash,或者是ram里面
定位到flash,一般是固化一些信息的。
定位到ram,比如定义一个数组,缓冲较大的数据或者是做内存池的时候,需要从固定的内存的位置,开始分配内存。
attribute((packed)) 取消优化编译对其。
attribute((aligned(n))) 按多少个字节对其。
attribute((weak)) 库函数里面使用,如果用户定义了,就是用用户的,否则使用默认定义的。
去掉编译优化,每次都都会从对应的地址里面拿到对应的数据,类似于指针变量。
只读,可以用来修饰,变量,数组,指针,函数的形参,函数的返回值。
需要注意的是使用const修饰函数的时候,如果函数的返回值,是指针变量。那么表示函数返回值指针,指向的内容是只读的。当这个函数作为右值的时候,左值的类型必须也是只读的,否则就会报警。
main.c: In function ‘main’:
main.c:14:11: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
int *p = fun1(a);
main.c: In function ‘main’:
main.c:14:11: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
int *p = fun1(a);
14 union
15 {
16 int i;
17 char a[2];
18 }*p, u;
19
20 u.i = 0;
21 p = &u;
22 p->a[0] = 0x39;
23 p->a[1] = 0x38;
24
25 printf("p->i:%x\n", p->i);
结果为:
p->i:3839
offsetof介绍
知道结构体类型,和结构体成员,就可以知道该成员在结构体中的偏移。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
说明:获得结构体(TYPE)的变量成员(MEMBER)在此结构体中的偏移量。
(01) ( (TYPE *)0 ) 将零转型为TYPE类型指针,即TYPE类型的指针的地址是0。
(02) ((TYPE *)0)->MEMBER 访问结构中的数据成员。
(03) &( ( (TYPE )0 )->MEMBER ) 取出数据成员的地址。由于TYPE的地址是0,这里获取到的地址就是相对MEMBER在TYPE中的偏移。
(04) (size_t)(&(((TYPE)0)->MEMBER)) 结果转换类型。对于32位系统而言,size_t是unsigned int类型;对于64位系统而言,size_t是unsigned long类型。
container_of介绍
知道结构体类型,结构体成员,结构体成员地址,就可以知道结构体的首地址
#define container_of(ptr, type, member) ({
const typeof( ((type *)0)->member ) *__mptr = (ptr);
(type *)( (char *)__mptr - offsetof(type,member) );})
说明:根据"结构体(type)变量"中的"域成员变量(member)的指针(ptr)“来获取指向整个结构体变量的指针。
(01) typeof( ( (type *)0)->member ) 取出member成员的变量类型。
(02) const typeof( ((type *)0)->member ) *__mptr = (ptr) 定义变量__mptr指针,并将ptr赋值给__mptr。经过这一步,__mptr为member数据类型的常量指针,其指向ptr所指向的地址。
(04) (char *)__mptr 将__mptr转换为字节型指针。
(05) offsetof(type,member)) 就是获取"member成员"在"结构体type"中的位置偏移。
(06) (char *)__mptr - offsetof(type,member)) 就是用来获取"结构体type"的指针的起始地址(为char *型指针)。
(07) (type *)( (char *)__mptr - offsetof(type,member) ) 就是将"char *类型的结构体type的指针"转换为"type *类型的结构体type的指针”。
定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
使用宏 va_end 来清理赋予 va_list 变量的内存。
#include
#include
double average(int num,...)
{
va_list valist;
double sum = 0.0;
int i;
/* 为 num 个参数初始化 valist */
va_start(valist, num);
/* 访问所有赋给 valist 的参数 */
for (i = 0; i < num; i++)
{
sum += va_arg(valist, int);
}
/* 清理为 valist 保留的内存 */
va_end(valist);
return sum/num;
}
int main()
{
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}
1.全局变量存在周周期长,将会占用更多的内存。
2.全局变量多处被调用,使全局变量的值变得不确定。
(1)用全局变量会增加程序的耦合性,会有一定移植麻烦,代码重用率低。
(2)全局变量,使用和更改他的地方多了以后,各个地方逻辑关系难以确定。
(3)过多的全局变量,大大降低程序的可读性,可维护性。
(4)容易造成命名冲突
有用的网站
https://blog.csdn.net/wangkaiblog/article/details/7720454