C语言memcmp函数详解:内存世界的“指纹比对器”

memcmp 是C语言标准库中的一个函数,定义在 头文件中。它用于比较两个内存区域(或字符串)的前 n 个字节。这个函数非常适合用于需要精确比较数据块的情况,比如比较数组、结构体或其他非字符串类型的数据。


函数原型

int memcmp(const void *ptr1, const void *ptr2, size_t num);

功能:像法医比对指纹一样,逐字节比较两块内存区域的内容是否完全相同。


入口参数:鉴证科的三要素

  1. ptr1 - 第一块内存(现场样本)
  • 类型:const void*
  • 可以是任何数据类型:数组、结构体、字符串等
  • 示例:int a[5] = {1,2,3,4,5}
  1. ptr2 - 第二块内存(比对样本)
  • 类型:const void*
  • 必须与ptr1类型一致才有比较意义
  • 示例:int b[5] = {1,2,3,4,5}
  1. num - 比较的字节数(检测精度)
  • 类型:size_t
  • 关键设置:sizeof(数据类型)*元素个数
  • 示例:比较5个int → 5*sizeof(int)

返回参数:鉴证报告

  • 返回值=0:两块内存完全相同
char str1[] = "ABC";
char str2[] = "ABC";
int result = memcmp(str1, str2, 3); // 返回0
  • 返回值<0:ptr1首个差异字节的ASCII值小于ptr2
// 0x41('A') vs 0x42('B')
memcmp("Apple", "Banana", 1); // 返回负数
  • 返回值>0:ptr1首个差异字节的ASCII值大于ptr2
// 0x43('C') vs 0x42('B')
memcmp("Cat", "Bat", 1); // 返回正数

实战用法:三大鉴证场景

场景1:网络数据包校验(防数据篡改)

// 发送端生成校验码
struct Packet {
    int seq;
    char data[100];
    uint32_t checksum; // 校验码
} send_pkt;

// 接收端验证校验码
uint32_t calc_checksum(const struct Packet *pkt) {
    // 计算前两个字段的校验和(跳过checksum字段)
    return crc32(pkt, offsetof(struct Packet, checksum));
}

void verify_packet(struct Packet *recv_pkt) {
    uint32_t real_checksum = calc_checksum(recv_pkt);
    // 比较内存中的校验码与计算的校验码(4字节)
    if (memcmp(&recv_pkt->checksum, &real_checksum, 4) != 0) {
        printf(" 数据包被篡改!");
    }
}

场景2:文件签名验证(验明正身)

// PNG文件头签名:89 50 4E 47 0D 0A 1A 0A
int is_png_file(const char *filename) {
    FILE *fp = fopen(filename, "rb");
    if (!fp) return 0;

    unsigned char header[8];
    fread(header, 1, 8, fp);
    fclose(fp);

    // 比对前8字节是否符合PNG签名
    return memcmp(header, "\x89PNG\r\n\x1A\n", 8) == 0;
}

/* 使用示例:
if (is_png_file("photo.png")) {
    printf("这是合法的PNG文件");
} */

场景3:结构体快速比对(高效验证)

typedef struct {
    int id;
    char name[20];
    double score;
} Student;

// 批量比对学生信息
void compare_students(const Student *s1, const Student *s2) {
    // 直接比较整个结构体的内存(需确保无填充字节差异)
    int cmp = memcmp(s1, s2, sizeof(Student));
    
    if (cmp == 0) {
        printf("学生信息完全相同\n");
    } else {
        printf("差异首次出现在第%d字节\n", abs(cmp));
    }
}

// 注意:结构体可能存在内存对齐填充,需确保编译设置一致

高阶技巧:鉴证专家的工具箱

  1. 浮点数比较的陷阱
float a = 1.0f, b = 1.0f;
// 直接比较可能失败(因精度问题)
if (memcmp(&a, &b, sizeof(float)) == 0) {
    printf("二进制表示的浮点数完全相同");
}
  1. 快速数组比对
int arr1[100], arr2[100];
// 比逐个元素比较快10倍以上
if (memcmp(arr1, arr2, sizeof(arr1)) == 0) {
    printf("数组内容完全一致");
}
  1. 安全密码比对(防时序攻击)
// 使用恒定时间比较(即使差异在第一个字符也完整比对)
int safe_compare(const char *input, const char *real) {
    size_t len = strlen(real);
    if (strlen(input) != len) return -1;
    
    int result = 0;
    for (size_t i = 0; i < len; ++i) {
        result |= input[i] ^ real[i]; // 逐位异或
    }
    return result; // 全等时返回0
}

注意事项:鉴证雷区

  1. 字节顺序问题(大小端差异)
// 0x12345678 在不同端序的存储:
// 大端:12 34 56 78
// 小端:78 56 34 12
int a = 0x12345678, b = 0x12345678;
memcmp(&a, &b, 4); // 在不同端序的机器上可能返回非0
  1. 结构体填充字节
#pragma pack(push, 1) // 关闭内存对齐
struct Test { char c; int i; }; 
#pragma pack(pop)

struct Test t1 = {'A', 100}, t2 = {'A', 100};
// 若编译时内存对齐设置不同,memcmp结果可能不同
  1. 浮点数的特殊值
double nan1 = 0.0/0.0, nan2 = 0.0/0.0;
memcmp(&nan1, &nan2, sizeof(double)); // 可能返回非0(NaN不相等)

总结:你的内存显微镜

对比项

memcmp(内存比对)

strncmp(字符串比对)

终止条件

比较指定字节数

遇到'\0'提前终止

适用类型

任意二进制数据

仅文本字符串

性能

O(n) 时间复杂度

O(n) 但可能提前终止

典型应用

数据校验、二进制协议

文件名比对、命令行参数解析


通过这份“内存鉴证指南”,你现在可以像专业鉴证人员一样使用memcmp函数,精准识别内存数据的异同!下次需要比对二进制数据时,记得启动你的内存显微镜!