C语言位操作和内存

公众号: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关键字,保存了静态变量。

  1. 初始化的全局变量和初始化的静态变量,在一块区域;
  2. 未初始化的全局变量和未初始化的静态变量,在一块区域,称作BSS(Block Started by Symbol:以符号开始的块);
  3. 静态变量的生命周期是整个源程序,而且只能被初始化一次,之后的初始化会被忽略。
    (如果不初始化,数值数据将被默认初始化为0, 字符型数据默认初始化为NULL)。
    整个数据区的数组,在程序结束后由系统统一销毁。
代码区(code)

用于存放编译后的可执行代码,二进制码,机器码。

堆和栈的区别
比较方面
管理方式由系统自动管理,以执行函数为单位由程序员手动控制
空间大小空间大小编译时确定(参数+局部变量)具有全局性,总体无大小限制
分配方式函数执行,系统自动分配;函数结束,系统立即自动回收使用new/malloc()手动分配释放;使用delete/free()手动释放
优点使用方便,不需要关心内存申请释放可以跨函数使用
缺点只能在函数内部使用容易造成内存泄露

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值