目录

一、C 语言内存函数

1. memcpy:内存复制函数

核心特性:

示例代码:

模拟实现:

2. memmove:处理重叠内存的复制函数

核心特性:

示例代码:

模拟实现:

3. memset:内存初始化函数

核心特性:

示例代码:

4. memcmp:内存比较函数

返回值规则:

示例代码:

二、数据在内存中的存储(第 18 讲)

1. 整数的存储方式

原码、反码、补码的转换:

2. 大小端字节序

字节序判断方法:

3. 浮点数的存储方式

存储细节:

示例解析:

三、典型例题解析

1. 字符类型存储问题

2. 无符号循环陷阱

四、面试考察要点


一、C 语言内存函数

1. memcpy:内存复制函数

void *memcpy(void *destination, const void *source, size_t num)用于从源内存地址复制指定字节数到目标内存地址。

核心特性:
  • 字节为单位复制数据,适用于任意类型的内存块。
  • 复制过程中不会因\0停止,严格按照num参数指定的字节数操作。
  • 不支持重叠内存区域,若源和目标地址重叠,结果未定义。
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
    int arr1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int arr2[10] = {0};
    // 复制前20字节(5个int类型元素)
    memcpy(arr2, arr1, 20);
    for (int i = 0; i < 10; i++) {
        printf("%d ", arr2[i]); // 输出:1 2 3 4 5 0 0 0 0 0
    }
    return 0;
}
模拟实现:
#include <assert.h>
void *my_memcpy(void *dst, const void *src, size_t count) {
    assert(dst && src); // 确保指针非空
    void *ret = dst; // 保存目标地址用于返回
    // 按字节逐个复制
    while (count--) {
        *(char *)dst = *(char *)src;
        dst = (char *)dst + 1;
        src = (char *)src + 1;
    }
    return ret;
}

2. memmove:处理重叠内存的复制函数

void *memmove(void *destination, const void *source, size_t num)memcpy功能相似,但支持源和目标内存区域重叠的场景

核心特性:
  • 通过判断地址关系选择复制方向,避免数据覆盖。
  • 当目标地址 <= 源地址 或 目标地址>= 源地址 +num时,正向复制(从低地址到高地址)。
  • 当内存区域重叠时,反向复制(从高地址到低地址)。
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
    int arr1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    // 将前20字节复制到从arr1+2开始的位置(重叠场景)
    memmove(arr1 + 2, arr1, 20);
    for (int i = 0; i < 10; i++) {
        printf("%d ", arr1[i]); // 输出:1 2 1 2 3 4 5 8 9 10
    }
    return 0;
}
模拟实现:
#include <assert.h>
void *my_memmove(void *dst, const void *src, size_t count) {
    assert(dst && src);
    void *ret = dst;
    if (dst <= src || (char *)dst >= (char *)src + count) {
        // 无重叠或目标在源右侧,正向复制
        while (count--) {
            *(char *)dst = *(char *)src;
            dst = (char *)dst + 1;
            src = (char *)src + 1;
        }
    } else {
        // 有重叠,反向复制
        dst = (char *)dst + count - 1;
        src = (char *)src + count - 1;
        while (count--) {
            *(char *)dst = *(char *)src;
            dst = (char *)dst - 1;
            src = (char *)src - 1;
        }
    }
    return ret;
}

3. memset:内存初始化函数

void *memset(void *ptr, int value, size_t num)用于将指定内存区域的前num个字节设置为value(以字节为单位)。

核心特性:
  • 按字节设置值,因此对非字符类型的数组可能产生非预期结果。
  • value参数虽为int,但实际只使用低 8 位(即unsigned char范围)。
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
    char str[] = "hello world";
    memset(str, 'x', 6); // 将前6字节设置为'x'
    printf("%s\n", str); // 输出:xxxxxxworld
    return 0;
}

4. memcmp:内存比较函数

int memcmp(const void *ptr1, const void *ptr2, size_t num)用于比较两个内存区域的前num个字节。

返回值规则:
  • ptr1 < ptr2(按无符号字节比较),返回负数。
  • ptr1 == ptr2,返回 0。
  • ptr1 > ptr2,返回正数。
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
    char buffer1[] = "DWgaOtP12df0";
    char buffer2[] = "DWGAOTP12DF0";
    int n = memcmp(buffer1, buffer2, sizeof(buffer1));
    if (n > 0)
        printf("buffer1 > buffer2\n");
    else if (n < 0)
        printf("buffer1 < buffer2\n");
    else
        printf("buffer1 == buffer2\n");
    return 0;
}

二、数据在内存中的存储(第 18 讲)

1. 整数的存储方式

整数在内存中以补码形式存储,原因如下:

  • 统一符号位和数值位的处理逻辑。
  • 简化加法和减法运算(CPU 仅需加法器)。
  • 补码与原码的转换规则统一,无需额外硬件。
原码、反码、补码的转换:
  • 正整数:原码 = 反码 = 补码。
  • 负整数
    • 原码:直接翻译的二进制(符号位为 1)。
    • 反码:原码符号位不变,其他位按位取反。
    • 补码:反码 + 1。

2. 大小端字节序

超过 1 字节的数据在内存中存储时,存在字节顺序问题:

  • 大端模式:数据的低位字节存于内存高地址,高位字节存于内存低地址。
  • 小端模式:数据的低位字节存于内存低地址,高位字节存于内存高地址。
字节序判断方法:
// 方法1:指针法
int check_sys() {
    int i = 1;
    return *(char *)&i; // 小端返回1,大端返回0
}

// 方法2:联合体法
int check_sys() {
    union {
        int i;
        char c;
    } un;
    un.i = 1;
    return un.c; // 小端返回1,大端返回0
}

3. 浮点数的存储方式

根据 IEEE 754 标准,浮点数表示为:\(V = (-1)^S \times M \times 2^E\)

  • S:符号位(0 为正,1 为负)。
  • M:有效数字(1 ≤ M < 2)。
  • E:指数位。
存储细节:
  • 32 位浮点数(float):1 位 S + 8 位 E + 23 位 M。
  • 64 位浮点数(double):1 位 S + 11 位 E + 52 位 M。
  • M 的存储:省略整数部分 1,仅存小数部分,读取时补回。
  • E 的存储:存入时加上中间值(8 位 E 加 127,11 位 E 加 1023)。
示例解析:
#include <stdio.h>
int main() {
    int n = 9;
    float *pFloat = (float *)&n;
    printf("n = %d\n", n); // 输出:9
    printf("*pFloat = %f\n", *pFloat); // 输出:0.000000(整数9的补码按浮点数解析极小)
    
    *pFloat = 9.0;
    printf("n = %d\n", n); // 输出:1091567616(浮点数9.0的存储按整数解析)
    printf("*pFloat = %f\n", *pFloat); // 输出:9.000000
    return 0;
}

三、典型例题解析

1. 字符类型存储问题

#include <stdio.h>
int main() {
    char a = -1;
    signed char b = -1;
    unsigned char c = -1;
    // a和b为有符号char,补码0xff对应-1;c为无符号,0xff对应255
    printf("a=%d, b=%d, c=%d\n", a, b, c); // 输出:a=-1, b=-1, c=255
    return 0;
}

2. 无符号循环陷阱

#include <stdio.h>
unsigned char i = 0;
int main() {
    // i为无符号char,范围0~255,i<=255恒成立,导致死循环
    for (i = 0; i <= 255; i++) {
        printf("hello world\n");
    }
    return 0;
}

四、面试考察要点

  1. 内存函数memcpymemmove的区别(重叠内存处理),memset的字节操作特性,memcmp的比较逻辑。
  2. 整数存储:补码的作用,原码、反码、补码的转换规则,符号位的影响。
  3. 字节序:大小端的判断方法,不同硬件架构的字节序差异(如 x86 为小端)。
  4. 浮点数存储:IEEE 754 标准的核心规则,整数与浮点数在内存中的解读差异。
  5. 边界问题:无符号类型的循环陷阱,字符类型的溢出处理(如char的范围 - 128~127)。

掌握内存操作函数和数据存储规则,是理解 C 语言底层逻辑的关键,尤其在系统编程和性能优化中至关重要。建议结合调试工具观察内存布局,加深对存储细节的理解。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐