C语言10-位运算与内存管理
一、位运算
位运算仅支持整型和字符型数据类型。
位运算符是直接对二进制数进行操作的运算符。
| 运算符 | 名称 | 运算规则 | 双目/单目 | 典型应用场景 | 注意事项 |
|---|---|---|---|---|---|
| & | 按位与 | 两位都为1时结果为1,否则为0 | 双目 | 清零指定位(某位与0相与) 判断某位是否为1 |
常与掩码配合使用 |
| | | 按位或 | 两位中至少有一位为1时结果为1,否则为0 | 双目 | 置1指定位(某位与1相或) 合并多个标志位 |
常与掩码配合使用 |
| ^ | 按位异或 | 两位不同(相异)时结果为1,相同则为0 | 双目 | 不借助临时变量交换两数 数据简单校验(如奇偶校验) 特定位取反(与1异或) |
相同数异或结果为0,与0异或保持不变 |
| ~ | 按位取反 | 将操作数的每一位翻转(0变1,1变0) | 单目 | 生成全1掩码(如 ~0)配合&实现位清零 |
注意操作数的有符号性,取反后符号位会改变,可能导致负数 |
| >> | 右移 | 将二进制位整体右移,低位溢出舍弃 | 双目 | 高效除以2的幂(仅对正数或逻辑右移) 提取高低字节 |
逻辑右移(无符号):高位补0 左操作数是需要被操作的数,右操作数是移动的位数 |
| << | 左移 | 将二进制位整体左移,高位溢出舍弃,低位补0 | 双目 | 高效乘以2的幂 组合构造数据(如将字节放入高位) |
有符号数左移可能改变符号位,导致未定义行为(溢出) 左操作数是需要被操作的数,右操作数是移动的位数 |
#include <stdio.h> int main(int argc, char **argv) { //两数进行交换 只能是整形变量 int a = 10; int b = 20; a ^= b; b ^= a; a ^= b; printf("a is %d ,b is %d\n", a, b); int testa = 0xa5; // a5 的bit6,3 置1 // 1 << 6--00000001 -> 01000000 testa = testa | (1 << 6) | (1 << 3); printf("testa is %d\n", testa); // a5 的bit3 清零 0 testa = 0xa5; testa = testa & ~(1 << 3); //等价于 testa &= ~(1 << 3) return 0; }
二、内存管理
2.1 内存空间介绍
当 ./a.out 运行后,操作系统会为其分配一个虚拟地址空间,从低地址到高地址通常分为以下几个主要区:
| 内存区域 | 存储内容 | 分配与释放 | 生命周期 | 关键特性 |
|---|---|---|---|---|
| 代码段 (.text) |
存放编译后的机器指令(C语言代码) | 系统自动 | 进程开始到结束 | 只读,防止程序意外修改自身指令 |
| 数据段 (.data / .bss / .rodata) |
细分如下: • .data:已初始化的全局变量和 static 变量• .bss:未初始化的全局变量和 static 变量(系统自动清零)• .rodata:字符串常量、 const 修饰的全局变量等 |
系统自动 | 进程开始到结束 |
.bss 段在可执行文件中不占磁盘空间,加载时由系统分配并清零 .rodata 段 只读 数据段 |
| 堆区 (Heap) |
动态申请的内存(如 malloc() / calloc() / realloc()) |
手动申请(malloc 族函数) 手动释放( free()) |
从申请到主动释放,或进程结束(由程序员决定生命周期) | • 空间较大(通常可达 2.9GB 左右) • 可动态增长 • 若忘记释放会产生内存泄漏 |
| 共享库映射区 (Memory Mapping Segment) |
动态链接库(如 libc.so 中的 printf、scanf 等函数实现)也可用于 mmap 映射文件 |
系统自动加载/卸载 | 动态库被加载到进程结束 | 可通过 ldd ./a.out 查看程序依赖的共享库 |
| 栈区 (Stack) |
局部变量、函数参数、返回地址、函数调用上下文 | 系统自动申请和释放 | 作用域结束(函数返回)即释放 | • 空间固定(通常默认 8MB) • 先进后出(LIFO) • 由编译器管理,效率高,但空间有限 |
注意:
① 地址增长方向:
- 栈 → 向低地址增长(由高地址向低地址扩展)
- 堆 → 向高地址增长(由低地址向高地址扩展)
- 两者相向而行,中间是共享库区域,最大限度利用虚拟地址空间
② 内存管理权归属:
栈:编译器自动管理,无需程序员干预
堆:程序员手动管理(申请/释放),更灵活但也更容易出错,可以在程序运行时,需要内存的时候,在堆中申请。
2.2 动态内存函数
① void *malloc(size_t size)
malloc是动态分配堆空间内存 ,size 参数是需要分配的内存大小(字节)。 返回值是分配到堆内存空间的首地址值。地址值需要保留好,直到调用free释放。 释放堆内存空间, 参数如要传入当时malloc的返回值。这个值要和但是分配空间的地址值一致。
② void free(void *ptr)
free是释放动态分配的堆内存,ptr是要释放的内存地址(必须是malloc返回的地址)
释放完后应立即将指针置为NULL(ptr =NULL), 不然ptr 是野指针。 也不要试图去访问原来的堆空间数据。 从逻辑上认为,堆空间数据生命周期结束。
③ 注意:
free()释放内存后,数据是否保留完全取决于后续内存的使用情况:若系统内存空间使用紧张,则free后的空间立马被使用,数据被覆盖。若空间使用不紧张,则数据保留不被覆盖#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { int a =20; // 局部变量 int* p = &a; // 指针指向了一个局部变量 printf("栈空间 p is %p\n",p); // q 接收堆空间后,指针本身就不应在发生变化。 指针指向的内容,任意修改 // int* const q = malloc(); int * q = (int*)malloc(sizeof(int)); // void* 0x1000,在堆区申请4字节空间 *q = *p; //q 指向堆内存,p 指向栈内存,两者独立 printf("堆空间 q is %p\n",q); printf("*q is %d ,*p is %d\n",*q,*p); free(q); // 归还空间 , q中依然存储堆空间的地址值 ,q为野指针 .释放后的空间不要在去访问 q = NULL; // 空指针。将q置为NULL,防止野指针 // q = &a;// 错误1, 丢失了堆空间的地址。 // free(q); // free(): invalid pointer 释放了栈空间 //q++; //指针偏移,指向1004 //free(q); // free(): invalid pointer //free() 只能释放 malloc 返回的原始地址 //释放非原始地址会导致堆结构破坏,程序崩溃 return 0; }#include <stdio.h> #include <string.h> #include <stdlib.h> void fun(char** p) // p 是二级指针 { *p = (char*)malloc(100); // 修改 main 中的 p } int main() { char* p = NULL; // 把一个指针当作参数传递,并且需要在被函数中修改指针本身,就需要传递本指针的地址,也就是二级指针。 fun(&p); // 传递 p 的地址(即二级指针) strcpy(p, "hello"); printf("p is %s\n", p); free(p); // 释放堆内存 p = NULL; // 防止野指针 return 0; }
2.3 内存泄漏(Memory Leak)
① 内存泄漏:程序在动态申请内存(堆区)后,没有及时释放或失去了对这块内存的控制,导致该内存无法被再次使用,直到程序结束。
② 仅申请堆内存空间而不释放,会导致系统内存耗尽,最终引发程序异常终止。
void func() { int *p = (int*)malloc(100 * sizeof(int)); // 使用 p ... // ❌ 忘记调用 free(p); } // 函数结束,p 被销毁,但堆内存仍被占用 说明:反复调用该函数,每次泄漏 100 个 int,内存耗尽,最终系统无可用内存 后续 malloc 返回 NULL,程序崩溃或行为异常 最终系统因频繁缺页中断而变慢,性能下降③ 申请堆空间后,若保存堆地址的指针意外被覆盖,将导致原有的堆空间无法访问,从而造成内存泄漏。
int *p = (int*)malloc(100 * sizeof(int)); // 使用 p ... p = (int*)malloc(200 * sizeof(int)); // ❌ 重新赋值,覆盖了原地址 // 第一块 100 个 int 的内存永远丢失了,无人指向,无法访问,无法释放注意:程序进程结束后,系统OS 会回收所有内存,但长期运行的程序(服务器)必须避免泄漏。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)