C语言学习笔记(十一):数据在内存中的存储

1. 内存概述

1.1 什么是内存?

内存(Memory)是计算机的重要组成部分,用于存储正在运行的程序和数据。C语言中,每个变量都占用一定大小的内存空间,理解数据在内存中的存储方式对于写出高效、正确的程序至关重要。

1.2 内存的基本单位

单位 大小 说明

位(bit) 0或1 最小的存储单位

字节(byte) 8 bits 基本寻址单位

字(word) 4或8 bytes CPU处理的基本单位

2. 整数在内存中的存储

2.1 原码、反码、补码

计算机中整数以补码形式存储。理解三种编码方式至关重要:

复制代码
#include <stdio.h>

 * 以 8 位整数为例(char类型)

 * 

 * 正数 5:

 * 原码:0000 0101

 * 反码:0000 0101(正数的反码与原码相同)

 * 补码:0000 0101(正数的补码与原码相同)

 * 

 * 负数 -5:

 * 原码:1000 0101(最高位为符号位,1表示负数)

 * 反码:1111 1010(符号位不变,其余位取反)

 * 补码:1111 1011(反码+1)

 */



void printBinary(unsigned char num) {

    for (int i = 7; i >= 0; i--) {

        printf("%d", (num >> i) & 1);

        if (i == 4) printf(" ");

    }

}



int main() {

    printf("===== 整数存储形式详解 =====\n\n");

    

    // 正数存储

    signed char positive = 5;

    printf("正数 %d 的存储形式:\n", positive);

    printf(" 二进制:");

    printBinary(positive);

    printf("\n 说明:正数的原码、反码、补码相同\n\n");

    

    // 负数存储

    signed char negative = -5;

    printf("负数 %d 的存储形式:\n", negative);

    printf(" 二进制:");

    printBinary(negative);

    printf("\n 这是 -5 的补码形式\n");

    printf(" 原码:1000 0101\n");

    printf(" 反码:1111 1010\n");

    printf(" 补码:1111 1011\n\n");

    

    // 验证:补码的补码是原码

    printf("验证:补码的补码是原码\n");

    unsigned char complement = 0xFB; // 1111 1011

    printf(" 补码 1111 1011 表示:%d\n", (signed char)complement);

    

    return 0;

}

2.2 不同整数类型的存储范围

复制代码
#include <stdio.h>

#include <limits.h>



int main() {

    printf("===== 整数类型存储范围 =====\n\n");

    

    // char 类型

    printf("char 类型(1字节,8位):\n");

    printf(" unsigned char: 0 ~ %u\n", UCHAR_MAX);

    printf(" signed char: %d ~ %d\n", SCHAR_MIN, SCHAR_MAX);

    printf(" char: 取决于编译器\n\n");

    

    // short 类型

    printf("short 类型(2字节,16位):\n");

    printf(" unsigned short: 0 ~ %u\n", USHRT_MAX);

    printf(" signed short: %d ~ %d\n\n", SHRT_MIN, SHRT_MAX);

    

    // int 类型

    printf("int 类型(4字节,32位):\n");

    printf(" unsigned int: 0 ~ %u\n", UINT_MAX);

    printf(" signed int: %d ~ %d\n\n", INT_MIN, INT_MAX);

    

    // 演示溢出

    printf("===== 整数溢出演示 =====\n\n");

    

    unsigned char uc = 255;

    printf("unsigned char uc = 255\n");

    printf("uc + 1 = %d (溢出回绕到0)\n", uc + 1);

    

    signed char sc = 127;

    printf("\nsigned char sc = 127\n");

    printf("sc + 1 = %d (溢出到负数)\n", sc + 1);

    

    return 0;

}

2.3 大小端字节序

复制代码
#include <stdio.h>



/*

 * 大端模式(Big-endian):高位字节存储在低地址

 * 小端模式(Little-endian):低位字节存储在低地址

 * 

 * 例如整数 0x12345678 在内存中的存储:

 * 

 * 地址: 0x100 0x101 0x102 0x103

 * 大端: 12 34 56 78

 * 小端: 78 56 34 12

 */



// 检测当前系统的大小端模式

void checkEndianness() {

    int num = 0x12345678;

    unsigned char *ptr = (unsigned char*)&num;

    

    printf("整数 0x12345678 在内存中的存储:\n");

    printf("地址\t\t值\n");

    for (int i = 0; i < sizeof(num); i++) {

        printf("%p\t0x%02x\n", ptr + i, ptr[i]);

    }

    

    if (ptr[0] == 0x78) {

        printf("\n当前系统是小端模式(Little-endian)\n");

    } else {

        printf("\n当前系统是大端模式(Big-endian)\n");

    }

}



// 大小端转换

unsigned int swapEndian(unsigned int num) {

    return ((num >> 24) & 0xFF) | // 最高字节移到最低

           ((num >> 8) & 0xFF00) | // 次高字节移到次低

           ((num << 8) & 0xFF0000) | // 次低字节移到次高

           ((num << 24) & 0xFF000000); // 最低字节移到最高

}



int main() {

    printf("===== 大小端字节序详解 =====\n\n");

    

    checkEndianness();

    

    printf("\n===== 大小端转换演示 =====\n\n");

    unsigned int original = 0x12345678;

    unsigned int converted = swapEndian(original);

    

    printf("原始值:0x%08x\n", original);

    printf("转换后:0x%08x\n", converted);

    

    return 0;

}

3. 浮点数在内存中的存储

3.1 IEEE 754 标准

浮点数遵循 IEEE 754 标准,以科学计数法形式存储:

复制代码
#include <stdio.h>



/*

 * 单精度浮点数(float,32位):

 * | 符号位(1) | 指数位(8) | 尾数位(23) |

 * 

 * 双精度浮点数(double,64位):

 * | 符号位(1) | 指数位(11) | 尾数位(52) |

 * 

 * 计算公式:(-1)^S × (1.M) × 2^(E-127)

 */



// 打印 float 的二进制表示

void printFloatBinary(float f) {

    unsigned int *ptr = (unsigned int*)&f;

    unsigned int num = *ptr;

    

    // 符号位

    int sign = (num >> 31) & 1;

    // 指数位

    int exponent = (num >> 23) & 0xFF;

    // 尾数位

    unsigned int mantissa = num & 0x7FFFFF;

    

    printf("符号位:%d\n", sign);

    printf("指数位:%d (0x%02X)\n", exponent, exponent);

    printf("尾数位:0x%06X\n", mantissa);

    

    printf("\n完整二进制:");

    for (int i = 31; i >= 0; i--) {

        printf("%d", (num >> i) & 1);

        if (i == 31 || i == 23) printf(" ");

    }

    printf("\n");

}



int main() {

    printf("===== 浮点数存储详解 =====\n\n");

    

    float f = 3.14f;

    printf("浮点数 %.2f 的内存表示:\n", f);

    printFloatBinary(f);

    

    // 特殊值

    printf("\n===== 特殊浮点数 =====\n\n");

    

    float infinity = 1.0f / 0.0f;

    printf("正无穷大:%f\n", infinity);

    

    float nan = 0.0f / 0.0f;

    printf("NaN(非数字):%f\n", nan);

    

    // 精度问题演示

    printf("\n===== 浮点数精度问题 =====\n\n");

    

    float a = 0.1f;

    float b = 0.2f;

    float c = a + b;

    

    printf("0.1 + 0.2 = %.20f\n", c);

    printf("理论上应该是 0.3,但由于二进制表示不精确,产生了微小误差\n");

    

    // 比较浮点数

    if (c == 0.3f) {

        printf("相等\n");

    } else {

        printf("不相等!误差:%.20f\n", c - 0.3f);

    }

    

    // 正确的浮点数比较方法

    float epsilon = 1e-6;

    if (fabs(c - 0.3f) < epsilon) {

        printf("使用容差比较:相等\n");

    }

    

    return 0;

}

3.2 浮点数与整数的转换

复制代码
#include <stdio.h>

#include <string.h>



// 查看浮点数的十六进制表示

void inspectFloat(float f) {

    unsigned int hex;

    memcpy(&hex, &f, sizeof(float));

    printf("%f 的十六进制:0x%08X\n", f, hex);

}



int main() {

    printf("===== 浮点数与整数转换 =====\n\n");

    

    float f1 = 1.0f;

    float f2 = 2.0f;

    float f3 = 0.5f;

    

    printf("常见浮点数的内存表示:\n");

    inspectFloat(f1);

    inspectFloat(f2);

    inspectFloat(f3);

    

    // 整数和浮点数的位模式对比

    printf("\n===== 整数和浮点数的位模式对比 =====\n\n");

    

    int i = 1;

    float f = 1.0f;

    

    printf("整数 1 的位模式:");

    for (int j = 31; j >= 0; j--) {

        printf("%d", (i >> j) & 1);

        if (j == 31 || j == 23) printf(" ");

    }

    printf("\n");

    

    printf("浮点数 1.0 的位模式:");

    unsigned int *ptr = (unsigned int*)&f;

    for (int j = 31; j >= 0; j--) {

        printf("%d", (*ptr >> j) & 1);

        if (j == 31 || j == 23) printf(" ");

    }

    printf("\n");

    printf("说明:即使数值相同,整数和浮点数的二进制表示完全不同!\n");

    

    return 0;

}

4. 指针与内存地址

4.1 指针的本质

指针是一个变量,存储的是内存地址。

复制代码
#include <stdio.h>



int main() {

    printf("===== 指针与内存地址 =====\n\n");

    

    int a = 10;

    int *p = &a;

    

    printf("变量 a 的值:%d\n", a);

    printf("变量 a 的地址:%p\n", &a);

    printf("指针 p 的值(存储的地址):%p\n", p);

    printf("指针 p 的地址:%p\n", &p);

    printf("通过指针访问的值:%d\n", *p);

    

    printf("\n===== 不同类型指针的大小 =====\n\n");

    

    printf("char* 大小:%zu 字节\n", sizeof(char*));

    printf("int* 大小:%zu 字节\n", sizeof(int*));

    printf("double* 大小:%zu 字节\n", sizeof(double*));

    printf("void* 大小:%zu 字节\n", sizeof(void*));

    printf("\n所有指针类型在相同平台下大小相同\n");

    

    return 0;

}

4.2 指针运算与内存布局

复制代码
#include <stdio.h>



int main() {

    printf("===== 指针运算与内存布局 =====\n\n");

    

    int arr[] = {10, 20, 30, 40, 50};

    int *p = arr;

    

    printf("数组地址:\n");

    for (int i = 0; i < 5; i++) {

        printf("&arr[%d] = %p, arr[%d] = %d\n", i, &arr[i], i, arr[i]);

    }

    

    printf("\n指针运算:\n");

    printf("p = %p, *p = %d\n", p, *p);

    printf("p + 1 = %p, *(p + 1) = %d\n", p + 1, *(p + 1));

    printf("p + 2 = %p, *(p + 2) = %d\n", p + 2, *(p + 2));

    

    printf("\n注意:p + 1 跳过了 %d 字节(一个int的大小)\n", sizeof(int));

    

    return 0;

}

5. 结构体的内存对齐

5.1 内存对齐规则

复制代码
#include <stdio.h>

#include <stddef.h>



/*

 * 内存对齐规则:

 * 1. 第一个成员在偏移量为0的位置

 * 2. 每个成员要对齐到其类型大小的整数倍

 * 3. 结构体总大小是最大成员大小的整数倍

 */



// 演示不同定义顺序导致的内存大小差异

struct StructA {

    char c; // 1字节

    int i; // 4字节,需要对齐到4的倍数

    short s; // 2字节

};

// 内存布局:c(1) + 填充(3) + i(4) + s(2) + 填充(2) = 12字节



struct StructB {

    int i; // 4字节

    char c; // 1字节

    short s; // 2字节,需要对齐到2的倍数

};

// 内存布局:i(4) + c(1) + 填充(1) + s(2) = 8字节



struct StructC {

    char c1; // 1字节

    short s; // 2字节,需要对齐到2的倍数

    int i; // 4字节,需要对齐到4的倍数

    char c2; // 1字节

};

// 内存布局:c1(1) + 填充(1) + s(2) + i(4) + c2(1) + 填充(3) = 12字节



int main() {

    printf("===== 结构体内存对齐 =====\n\n");

    

    printf("struct A 大小:%zu 字节\n", sizeof(struct StructA));

    printf("struct B 大小:%zu 字节\n", sizeof(struct StructB));

    printf("struct C 大小:%zu 字节\n", sizeof(struct StructC));

    

    printf("\n说明:相同成员,不同顺序,占用内存不同!\n");

    printf("建议:将占用空间大的成员放在前面,节省内存\n");

    

    printf("\n===== 查看成员偏移量 =====\n\n");

    

    printf("struct A:\n");

    printf(" c 偏移量:%zu\n", offsetof(struct StructA, c));

    printf(" i 偏移量:%zu\n", offsetof(struct StructA, i));

    printf(" s 偏移量:%zu\n", offsetof(struct StructA, s));

    

    printf("\nstruct B:\n");

    printf(" i 偏移量:%zu\n", offsetof(struct StructB, i));

    printf(" c 偏移量:%zu\n", offsetof(struct StructB, c));

    printf(" s 偏移量:%zu\n", offsetof(struct StructB, s));

    

    return 0;

}

5.2 强制对齐和紧凑结构

复制代码
#include <stdio.h>



// 使用 pragma pack 强制紧凑排列

#pragma pack(push, 1) // 按1字节对齐

struct PackedStruct {

    char c;

    int i;

    short s;

};

#pragma pack(pop)



// 使用 attribute 指定对齐(GCC)

#ifdef __GNUC__

struct AlignedStruct {

    char c;

    int i;

    short s;

} __attribute__((aligned(8))); // 8字节对齐

#endif



int main() {

    printf("===== 对齐控制 =====\n\n");

    

    printf("正常结构体大小:%zu 字节\n", sizeof(struct StructA));

    printf("紧凑结构体大小:%zu 字节(使用 #pragma pack(1))\n", 

           sizeof(struct PackedStruct));

    

    #ifdef __GNUC__

    printf("指定8字节对齐:%zu 字节\n", sizeof(struct AlignedStruct));

    #endif

    

    printf("\n紧凑结构体的成员布局:\n");

    printf("c 偏移量:%zu\n", offsetof(struct PackedStruct, c));

    printf("i 偏移量:%zu\n", offsetof(struct PackedStruct, i));

    printf("s 偏移量:%zu\n", offsetof(struct PackedStruct, s));

    

    return 0;

}

6. 动态内存分配

6.1 堆内存的分配与释放

复制代码
#include <stdio.h>

#include <stdlib.h>

#include <string.h>



int main() {

    printf("===== 动态内存分配 =====\n\n");

    

    // malloc - 分配内存(不初始化)

    int *p1 = (int*)malloc(5 * sizeof(int));

    if (p1 == NULL) {

        printf("内存分配失败\n");

        return 1;

    }

    printf("malloc: 分配了 5 个 int 的空间,地址:%p\n", p1);

    

    // calloc - 分配并初始化为0

    int *p2 = (int*)calloc(5, sizeof(int));

    printf("calloc: 分配并清零,前几个值:%d, %d, %d\n", p2[0], p2[1], p2[2]);

    

    // realloc - 重新调整大小

    int *p3 = (int*)realloc(p1, 10 * sizeof(int));

    if (p3 != NULL) {

        printf("realloc: 扩展到 10 个 int,新地址:%p\n", p3);

        p1 = p3;

    }

    

    // 释放内存

    free(p1);

    free(p2);

    printf("\n内存已释放\n");

    

    return 0;

}

6.2 常见错误示例

复制代码
#include <stdio.h>

#include <stdlib.h>



// 错误1:内存泄漏

void memoryLeak() {

    int *p = (int*)malloc(sizeof(int));

    *p = 10;

    // 忘记调用 free(p) - 内存泄漏

}



// 错误2:悬空指针

void danglingPointer() {

    int *p = (int*)malloc(sizeof(int));

    *p = 10;

    free(p);

    // *p = 20; // 错误!使用已释放的内存

}



// 错误3:重复释放

void doubleFree() {

    int *p = (int*)malloc(sizeof(int));

    free(p);

    // free(p); // 错误!重复释放

}



// 错误4:缓冲区溢出

void bufferOverflow() {

    int *p = (int*)malloc(5 * sizeof(int));

    // p[5] = 100; // 错误!越界访问

    free(p);

}



int main() {

    printf("===== 动态内存常见错误 =====\n\n");

    printf("1. 内存泄漏:分配后忘记释放\n");

    printf("2. 悬空指针:使用已释放的内存\n");

    printf("3. 重复释放:多次释放同一内存\n");

    printf("4. 缓冲区溢出:访问越界\n");

    printf("\n使用 valgrind 等工具可以检测这些问题\n");

    

    return 0;

}

7. 栈内存与堆内存对比

复制代码
#include <stdio.h>

#include <stdlib.h>

#include <string.h>



// 栈内存分配

void stackAllocation() {

    int stackArr[100000]; // 栈上分配,自动释放

    printf("栈内存分配成功\n");

}



// 堆内存分配

void heapAllocation() {

    int *heapArr = (int*)malloc(100000 * sizeof(int));

    if (heapArr != NULL) {

        printf("堆内存分配成功\n");

        free(heapArr);

    }

}



int main() {

    printf("===== 栈内存 vs 堆内存 =====\n\n");

    

    printf("栈内存特点:\n");

    printf(" - 自动分配和释放\n");

    printf(" - 大小固定(通常几MB)\n");

    printf(" - 访问速度快\n");

    printf(" - 存在栈溢出风险\n\n");

    

    printf("堆内存特点:\n");

    printf(" - 手动分配和释放\n");

    printf(" - 大小灵活(受系统内存限制)\n");

    printf(" - 访问速度相对慢\n");

    printf(" - 需要手动管理,容易内存泄漏\n\n");

    

    printf("内存布局(从高地址到低地址):\n");

    printf(" +-----------------+ 高地址\n");

    printf(" | 栈(Stack) |\n");

    printf(" +-----------------+\n");

    printf(" | ↓ |\n");

    printf(" | ↑ |\n");

    printf(" +-----------------+\n");

    printf(" | 堆(Heap) |\n");

    printf(" +-----------------+\n");

    printf(" | BSS 段 | 未初始化数据\n");

    printf(" +-----------------+\n");

    printf(" | 数据段 | 初始化数据\n");

    printf(" +-----------------+\n");

    printf(" | 代码段 | 可执行代码\n");

    printf(" +-----------------+ 低地址\n");

    

    return 0;

}

8. 实战案例:实现内存池

复制代码
#include <stdio.h>

#include <stdlib.h>

#include <string.h>



#define POOL_SIZE 1024

#define ALIGNMENT 8



// 简单的内存池实现

typedef struct MemoryPool {

    unsigned char buffer[POOL_SIZE];

    size_t offset;

} MemoryPool;



// 初始化内存池

void initPool(MemoryPool *pool) {

    memset(pool->buffer, 0, POOL_SIZE);

    pool->offset = 0;

}



// 对齐分配

void* poolAlloc(MemoryPool *pool, size_t size) {

    // 对齐到 ALIGNMENT 的倍数

    size_t alignedSize = (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1);

    

    if (pool->offset + alignedSize > POOL_SIZE) {

        printf("内存池空间不足!\n");

        return NULL;

    }

    

    void *ptr = pool->buffer + pool->offset;

    pool->offset += alignedSize;

    return ptr;

}



// 重置内存池

void poolReset(MemoryPool *pool) {

    pool->offset = 0;

}



// 获取剩余空间

size_t poolRemaining(MemoryPool *pool) {

    return POOL_SIZE - pool->offset;

}



int main() {

    printf("===== 内存池实现 =====\n\n");

    

    MemoryPool pool;

    initPool(&pool);

    

    printf("内存池初始化完成,大小:%d 字节\n", POOL_SIZE);

    printf("剩余空间:%zu 字节\n\n", poolRemaining(&pool));

    

    // 分配内存

    int *a = (int*)poolAlloc(&pool, sizeof(int));

    double *b = (double*)poolAlloc(&pool, sizeof(double));

    char *str = (char*)poolAlloc(&pool, 100);

    

    if (a && b && str) {

        *a = 42;

        *b = 3.14159;

        strcpy(str, "Hello, Memory Pool!");

        

        printf("分配成功:\n");

        printf(" int: %d\n", *a);

        printf(" double: %.5f\n", *b);

        printf(" string: %s\n", str);

        printf(" 剩余空间:%zu 字节\n", poolRemaining(&pool));

    }

    

    printf("\n重置内存池...\n");

    poolReset(&pool);

    printf("重置后剩余空间:%zu 字节\n", poolRemaining(&pool));

    

    return 0;

}

9. 调试技巧

9.1 打印内存内容

复制代码
#include <stdio.h>



// 以十六进制打印内存区域

void printMemory(const void *ptr, size_t size) {

    const unsigned char *bytes = (const unsigned char*)ptr;

    

    printf("内存地址:%p\n", ptr);

    printf("字节内容:");

    for (size_t i = 0; i < size; i++) {

        printf("%02X ", bytes[i]);

        if ((i + 1) % 8 == 0 && i + 1 < size) {

            printf("\n ");

        }

    }

    printf("\n");

}



int main() {

    printf("===== 内存调试工具 =====\n\n");

    

    int arr[] = {0x12345678, 0x90ABCDEF};

    printf("数组内容:\n");

    printMemory(arr, sizeof(arr));

    

    struct {

        int a;

        char b;

        short c;

    } s = {0x12345678, 'A', 0x1234};

    

    printf("\n结构体内容:\n");

    printMemory(&s, sizeof(s));

    

    return 0;

}

10. 总结

10.1 核心知识点回顾

数据类型 存储方式 关键点

整数 补码 注意溢出和符号位

浮点数 IEEE 754 精度问题,特殊值

指针 地址 大小固定,类型决定步长

结构体 内存对齐 顺序影响大小

数组 连续内存 数组名即首地址

10.2 最佳实践

  1. 整数运算:注意溢出,使用适当类型

  2. 浮点数:不要直接比较相等,使用容差

  3. 指针:初始化,检查NULL,避免悬空

  4. 动态内存:配对 malloc/free,防止泄漏

  5. 结构体:合理安排成员顺序,节省内存

  6. 字节序:网络传输时注意转换

10.3 常见陷阱

· ❌ 使用未初始化的内存

· ❌ 越界访问数组

· ❌ 忘记释放动态内存

· ❌ 使用已释放的内存

· ❌ 忽略内存对齐

· ❌ 混淆大小端字节序

记住:理解内存是C语言的核心,只有真正理解数据如何存储,才能写出高效、可靠的程序!

相关推荐
恒拓高科WorkPlus2 小时前
私有化视频会议如何重塑企业高效协作体验
经验分享
观书喜夜长2 小时前
大模型应用开发学习-基于langchain框架做一个个人文档问答助手
python·学习·idea
承渊政道2 小时前
【优选算法】(实战体验滑动窗口的奇妙之旅)
c语言·c++·笔记·学习·算法·leetcode·visual studio
huohuopro2 小时前
UML的概念和主图学习
学习·uml
C羊驼2 小时前
C语言学习笔记(十):操作符
c语言·开发语言·经验分享·笔记·学习
鹭天2 小时前
RAG学习笔记
笔记·学习
恒拓高科WorkPlus2 小时前
BeeWorks:高效安全,重塑企业即时通信新体验 - BeeWorks
经验分享
arvin_xiaoting3 小时前
OpenClaw学习总结_I_核心架构_6:Compaction详解
学习·系统架构·学习总结·ai agent·compaction·openclaw
存储服务专家StorageExpert3 小时前
NetApp NVME SSD 盘的学习笔记
运维·服务器·笔记·学习·存储维护·emc存储·netapp