公众号:CppCoding
位操作
1.位运算
No. | 操作符 | 功能 |
---|---|---|
1 | & | 按位与 |
2 | 按位或 | |
3 | ~ | 按位取反 |
4 | ^ | 按位异或 |
异或的用处:
可以找出数列中只出现一次的数字
https://blog.csdn.net/xiaodalei/article/details/7013499##2
注:按位或的操作符是 |
2.运算规则
逻辑运算与按位运算
1.逻辑运算结果只有0和1两种值,按位运算有多种值。
2.逻辑运算相当于把所有的非零值都变成1,再按位运算。
移位运算
No. | 操作符 | 功能 |
---|---|---|
1 | << | 左移 |
2 | >> | 右移 |
- 左移
i<<j表示i中所有位向左移动j个位置,右边填入0。
所有小于int的类型,移位以int大小执行,结果为int。 - 右移
i>>j表示i中所有位向右移动j个位置,
对于unsigned类型,左边填入0;对于signed类型,左边填入符号位。
所有小于int的类型,移位以int大小执行,结果为int。
注:
左移和右移并不是简单的乘以二和除以二
除以二详见:https://blog.csdn.net/FlushHip/article/details/82495034
乘以二移位不能包含一
位域
-
说明
位域是又称作位段,是把一个字节中的二进位划分为几个不同的区域。 -
作用
节省空间,有些信息不需要占用一个完整的字节。 -
调用
位域的调用方式和结构成员调用方式相同,形式为:位域变量名 . 位域名
-
定义
位域的定义和结构体相似 -
语法
struct 位域结构名{
类型 位域名:位域长度;
};
为了保证位域的可以移植性,成员类型通常为unsigned int和int,C99可以使用bool。
示例
struct Byte{
unsigned int b1:1;
unsigned int b2:1;
unsigned int b3:1;
unsigned int b4:1;
unsigned int b5:1;
unsigned int b6:1;
unsigned int b7:1;
unsigned int b8:1;
}; //此位域占四个字节的空间
位域变量
定义和使用位域变量与结构体相同。每个域有一个域名,允许在程序中按域名进行操作。
void printByte(struct Byte a){
printf("%d",a.b1);
printf("%d",a.b2);
printf("%d",a.b3);
printf("%d",a.b4);
printf("%d",a.b5);
printf("%d",a.b6);
printf("%d",a.b7);
printf("%d\n",a.b8);
}
int main () {
struct Byte a;
printByte(a);
struct Byte b = {1,0,1,0,1,0,1,0,};
printByte(b);
return 0;
}
举例
struct time
{
int a:8;
int b:2;
int c:6;
}data; //data为time变量,共占用四个字节
- 说明
在位域中存储数据需要将数据转化为二进制数进行比较,若位段不够长,则不能存储,一个位段最长为8位。
注
1.一个位域必须存储在同一个字节中,不能跨两个字节,如果一个字节所剩空间不够存放下一位域时,应该从下一个单元开 始,当然也可以有意调整使其从下一个单元开始;
2.位域长度不能大于一个字节的长度,即不能超过8位;
3.可以定义无名位域用来做填充或调整位置。
综上:从本质上说,位域是一种结构类型,只不过其成员是按照二进制位来分配的。
内存
字节对齐的作用和原因:
对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐,其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据,显然在读取效率上下降很多。
结构体对齐
直入主题,要判断一个结构体所占的空间大小,大体来说分三步走:
1.先确定实际对齐单位,其由以下三个因素决定
(1) CPU周期
WIN vs qt 默认8字节对齐
Linux 32位 默认4字节对齐,64位默认8字节对齐
(2) 结构体最大成员(基本数据类型变量)
(3)预编译指令 # pragma pack(n)手动设置 n–只能填1 2 4 8 16
2.除结构体的第一个成员外,其他所有的成员的地址相对于结构体地址(即它首个成员的地址)的偏移量必须为实际对齐单位或自身大小的整数倍(取两者中小的那个)
3.结构体的整体大小必须为实际对齐单位的整数倍。
struct S1{
char c1;
char c2;
int n;
};
struct S2{
char c1;
int n;
char c2;
};
printf("sizeof(struct S1) = %ld\n",sizeof(struct S1));
printf("sizeof(struct S2) = %ld\n",sizeof(struct S2));
在C语言里,结构体所占的内存是连续的,但是各个成员之间的地址不一定是连续的。所以就出现了"字节对齐"。
四个重要的概念:
1.数据类型自身的对齐值:对于char型的数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4个字节。
2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3.指定对齐值:#pragma pack (value)时指定的对齐value。
4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
字节对齐默认原则
- 结构体变量的大小,一定是其最大的数据类型的大小的整数倍,如果某个数据类型大小不够,就填充字节。
- 结构体变量的地址,一定和其第一个成员的地址是相同的。
struct Box{
int height;
char a[10];
double width;
char type;
};
int main(void) {
struct Box box;
printf("box = %p\n", &box);
printf("box.height = %p\n", &box.height);
printf("box.a = %p\n", box.a);
printf("box.width = %p\n", &box.width);
printf("box.type = %p\n", &box.type);
printf("box = %d\n", sizeof(box));
return 0;
}
#include <stdio.h>
#include <stdlib.h>
void f1(){
int f1local1;
int f1local2;
static int f1static1;
static int f1static2;
int* f1dynamic1 = malloc(sizeof(int));
int* f1dynamic2 = malloc(sizeof(int));
printf("--------------f1()----------------\n");
printf("&f1local1 = %p\n",&f1local1);
printf("&f1local2 = %p\n",&f1local2);
printf("&f1static1 = %p\n",&f1static1);
printf("&f1static2 = %p\n",&f1static2);
printf("f1dynamic1 = %p\n",f1dynamic1);
printf("f1dynamic2 = %p\n",f1dynamic2);
printf("\"f1string1\" = %p\n","f1string1");
printf("\"f1string2\" = %p\n","f1string2");
printf("--------------f1()----------------\n");
free(f1dynamic1);
free(f1dynamic2);
}
void f2(){
int f2local1;
int f2local2;
static int f2static1;
static int f2static2;
int* f2dynamic1 = malloc(sizeof(int));
int* f2dynamic2 = malloc(sizeof(int));
printf("--------------f2()----------------\n");
printf("&f2local1 = %p\n",&f2local1);
printf("&f2local2 = %p\n",&f2local2);
printf("&f2static1 = %p\n",&f2static1);
printf("&f2static2 = %p\n",&f2static2);
printf("f2dynamic1 = %p\n",f2dynamic1);
printf("f2dynamic2 = %p\n",f2dynamic2);
printf("\"f2string1\" = %p\n","f2string1");
printf("\"f2string2\" = %p\n","f2string2");
printf("--------------f2()----------------\n");
free(f2dynamic1);
free(f2dynamic2);
}
int static1;
int static2;
int main(){
int local1;
int local2;
int* dynamic1 = malloc(sizeof(int));
int* dynamic2 = malloc(sizeof(int));
printf("&local1 = %p\n",&local1);
printf("&local2 = %p\n",&local2);
printf("&static1 = %p\n",&static1);
printf("&static2 = %p\n",&static2);
printf("dynamic1 = %p\n",dynamic1);
printf("dynamic2 = %p\n",dynamic2);
printf("\"string1\" = %p\n","string1");
printf("\"string2\" = %p\n","string2");
printf("&f1 = %p\n",&f1);
printf("&f2 = %p\n",&f2);
f1();
f2();
}
分析上述代码的结果
&local1 = 0x7ffc8b8120fc
&local2 = 0x7ffc8b8120f8
&static1 = 0x601060
&static2 = 0x601064
dynamic1 = 0x1d10010
dynamic2 = 0x1d10030
"string1" = 0x400b8f
"string2" = 0x400ba7
&f1 = 0x40060d
&f2 = 0x400707
--------------f1()----------------
&f1local1 = 0x7ffc8b8120cc
&f1local2 = 0x7ffc8b8120c8
&f1static1 = 0x601050
&f1static2 = 0x601054
f1dynamic1 = 0x1d10050
f1dynamic2 = 0x1d10070
"f1string1" = 0x400a2f
"f1string2" = 0x400a4b
--------------f1()----------------
--------------f2()----------------
&f2local1 = 0x7ffc8b8120cc
&f2local2 = 0x7ffc8b8120c8
&f2static1 = 0x601058
&f2static2 = 0x60105c
f2dynamic1 = 0x1d10070
f2dynamic2 = 0x1d10050
"f2string1" = 0x400af7
"f2string2" = 0x400b13
--------------f2()----------------
上面结果每次执行结果会有所变化,不同计算机结果也会不同。但是会有如下规律:
局部变量、静态变量、动态分配内存、字符串常量与函数分别放在一起,即使在不同的函数中。
变量的存放地址大小有如下特点:
1.字符串常量与代码 < 静态变量 < 动态分配内存 < 局部变量
2.静态变量、动态分配内存、字符串常量与函数的相邻变量地址是递增的。局部变量相邻变量地址是递减的。
3.字符串常量与函数是在一起的。
上面说明代码在内存中是按类型存放在不同的区里面的。
栈区(stack)
由编译器自动分配和释放,主要是存放函数参数的值,局部变量的值。
堆区(heap)
由程序员自己申请分配和释放,需要malloc()、calloc()、realloc()函数来申请,用free()函数来释放如果不释放,可能出现指针悬空/野指针。
函数不能返回指向栈区的指针,但是可以返回指向堆区的指针。
数据区(data)
变量标有static关键字,保存了静态变量。
- 初始化的全局变量和初始化的静态变量,在一块区域;
- 未初始化的全局变量和未初始化的静态变量,在一块区域,称作BSS(Block Started by Symbol:以符号开始的块);
- 静态变量的生命周期是整个源程序,而且只能被初始化一次,之后的初始化会被忽略。
(如果不初始化,数值数据将被默认初始化为0, 字符型数据默认初始化为NULL)。
整个数据区的数组,在程序结束后由系统统一销毁。
代码区(code)
用于存放编译后的可执行代码,二进制码,机器码。
堆和栈的区别
比较方面 | 栈 | 堆 |
---|---|---|
管理方式 | 由系统自动管理,以执行函数为单位 | 由程序员手动控制 |
空间大小 | 空间大小编译时确定(参数+局部变量) | 具有全局性,总体无大小限制 |
分配方式 | 函数执行,系统自动分配;函数结束,系统立即自动回收 | 使用new/malloc()手动分配释放;使用delete/free()手动释放 |
优点 | 使用方便,不需要关心内存申请释放 | 可以跨函数使用 |
缺点 | 只能在函数内部使用 | 容易造成内存泄露 |