一、位运算

位运算仅支持整型和字符型数据类型。

位运算符是直接对二进制数进行操作的运算符。

运算符 名称 运算规则 双目/单目 典型应用场景 注意事项
& 按位与 两位都为1时结果为1,否则为0 双目 清零指定位(某位与0相与)
判断某位是否为1
常与掩码配合使用
| 按位或 两位中至少有一位为1时结果为1,否则为0 双目 置1指定位(某位与1相或)
合并多个标志位
常与掩码配合使用
^ 按位异或 两位不同(相异)时结果为1,相同则为0 双目 不借助临时变量交换两数
数据简单校验(如奇偶校验)
特定位取反(与1异或)
相同数异或结果为0,与0异或保持不变
~ 按位取反 将操作数的每一位翻转(0变1,1变0) 单目 生成全1掩码(如 ~0
配合&实现位清零
注意操作数的有符号性,取反后符号位会改变,可能导致负数
>> 右移 将二进制位整体右移,低位溢出舍弃 双目 高效除以2的幂(仅对正数或逻辑右移)
提取高低字节

逻辑右移(无符号):高位补0
算术右移(有符号):高位补符号位(负数补1,正数补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 中的 printfscanf 等函数实现)
也可用于 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 会回收所有内存,但长期运行的程序(服务器)必须避免泄漏。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐