从Redis 8.4.0源码看快速排序(1) 宏函数min和swapcode

目录

1.选用的Redis版本

2.需要研究的文件

3.pqsort.h

4.pqsort.c

头文件

两个函数的声明

min宏函数

swapcode宏函数

测试代码

运行结果

从预处理看看动态生成的swapcode函数

运行结果

注意swapcode细节

宏函数最外侧有花括号,这是不可省略的!

[题外话: 补充do-while(0)的大括号作用](#题外话: 补充do-while(0)的大括号作用)

例题

代码1(错误)

代码2(错误)

代码3(正确)


1.选用的Redis版本

2025年11月18日发布的版本

https://github.com/redis/redis/releases/tag/8.4.0

源代码压缩文件下载链接: https://github.com/redis/redis/archive/refs/tags/8.4.0.zip

2.需要研究的文件

bash 复制代码
redis-8.4.0/src
├──......
├── pqsort.c
├── pqsort.h
├──......

pqsort.c的前几行的注释:

cpp 复制代码
/* The following is the NetBSD libc qsort implementation modified in order to
 * support partial sorting of ranges for Redis.
 *
 * Copyright(C) 2009-current Redis Ltd.. All rights reserved.
 *
 * The original copyright notice follows. */

从中可以看出:pqsort从NetBSD的C库的qsort修改而来,pqsort实现的是部分范围排序(partial sorting of ranges)

下面逐行分析

3.pqsort.h

cpp 复制代码
#ifndef __PQSORT_H
#define __PQSORT_H

void
pqsort(void *a, size_t n, size_t es,
    int (*cmp) (const void *, const void *), size_t lrange, size_t rrange);

#endif

给出pqsort的使用方法,参数的含义大致分析:

a 应该是指向需要排序的数组吧?

n 暂不清楚

es 暂不清楚

cmp 函数指针,其指向的函数用于指定比较规则?

lrange 可能是a中需要排序部分的左边界?

rrange 可能是a中需要排序部分的右边界?

深入代码后会逐渐理解这些参数的含义

4.pqsort.c

头文件

cpp 复制代码
#include <sys/types.h>
#include <stdint.h>
#include <errno.h>
#include <stdlib.h>

两个函数的声明

cpp 复制代码
static inline char	*med3 (char *, char *, char *,
    int (*)(const void *, const void *));
static inline void	 swapfunc (char *, char *, size_t, int);

med3函数暂时不清楚含义,但swapfunc可以猜出应该是用于交换的函数

min宏函数

cpp 复制代码
#define min(a, b)	(a) < (b) ? a : b

作用: 选出a和b中最小的

swapcode宏函数

cpp 复制代码
#define swapcode(TYPE, parmi, parmj, n) { 		\
	size_t i = (n) / sizeof (TYPE); 		\
	TYPE *pi = (TYPE *)(void *)(parmi); 		\
	TYPE *pj = (TYPE *)(void *)(parmj); 		\
	do { 						\
		TYPE	t = *pi;			\
		*pi++ = *pj;				\
		*pj++ = t;				\
        } while (--i > 0);				\
}

猜测:

TYPE: s数组元素的类型

parmi,parmj: 各自指向需要交换的两个数组

n: 暂不清楚

这种写法之前在95.【C语言】解析预处理(3)文章的应用2:批量定义函数 提到过,类似C++的模版 ,显然swapcode作用是批量交换两个数组中的元素

算法:

i存储交换次数

pi,pj各自指向需要交换的两个数组

批量交换使用do-while,创建第3个临时变量用于交换

交换后,pi和pj个各向后移动一个元素的距离

那么可以推出n的含义: 数组中需要交换的元素占用的字节数,因为(n) / sizeof (TYPE)

测试代码

cpp 复制代码
#include <stdio.h>
#include <stddef.h>
#define swapcode(TYPE, parmi, parmj, n) { 		\
	size_t i = (n) / sizeof (TYPE); 		\
	TYPE *pi = (TYPE *)(void *)(parmi); 		\
	TYPE *pj = (TYPE *)(void *)(parmj); 		\
	do { 						\
		TYPE	t = *pi;			\
		*pi++ = *pj;				\
		*pj++ = t;				\
        } while (--i > 0);				\
}

int main()
{
    int arr1[]={1,2,3};
    int arr2[]={4,5,6};
    printf("交换前:\n");
    for (int i=0;i<sizeof(arr1)/sizeof(int);i++)
        printf("arr1[%d]=%d arr2[%d]=%d\n",i,arr1[i],i,arr2[i]);
    printf("\n");
    swapcode(int,arr1,arr2,sizeof(arr1));
    printf("交换后:\n");
    for (int i=0;i<sizeof(arr1)/sizeof(int);i++)
         printf("arr1[%d]=%d arr2[%d]=%d\n",i,arr1[i],i,arr2[i]);
    return 0;
}
运行结果

从预处理看看动态生成的swapcode函数

cpp 复制代码
#define swapcode(TYPE, parmi, parmj, n) { 		\
	size_t i = (n) / sizeof (TYPE); 		\
	TYPE *pi = (TYPE *)(void *)(parmi); 		\
	TYPE *pj = (TYPE *)(void *)(parmj); 		\
	do { 						\
		TYPE	t = *pi;			\
		*pi++ = *pj;				\
		*pj++ = t;				\
        } while (--i > 0);				\
}

int main()
{
    int arr1[]={1,2,3};
    int arr2[]={4,5,6};
    swapcode(int,arr1,arr2,sizeof(arr1));

    char arr3[]={'a','b','c'};
    char arr4[]={'d','e','f'};
    swapcode(char,arr3,arr4,sizeof(arr3));
    
    return 0;
}

让gcc只执行预处理这一步,编译以上代码:

bash 复制代码
gcc -E ./test_redis_swap.c -o ./test_redis_swap.i
运行结果

swapcode(int,arr1,arr2,sizeof(arr1))和swapcode(char,arr3,arr4,sizeof(arr3))被gcc展开,生成的test_redis_swap.i为:

cpp 复制代码
# 0 "./test_redis_swap.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "./test_redis_swap.c"
# 12 "./test_redis_swap.c"
int main()
{
    int arr1[]={1,2,3};
    int arr2[]={4,5,6};
    { size_t i = (sizeof(arr1)) / sizeof (int); int *pi = (int *)(void *)(arr1); int *pj = (int *)(void *)(arr2); do { int t = *pi; *pi++ = *pj; *pj++ = t; } while (--i > 0); };

    char arr3[]={'a','b','c'};
    char arr4[]={'d','e','f'};
    { size_t i = (sizeof(arr3)) / sizeof (char); char *pi = (char *)(void *)(arr3); char *pj = (char *)(void *)(arr4); do { char t = *pi; *pi++ = *pj; *pj++ = t; } while (--i > 0); };

    return 0;
}

注意swapcode细节

宏函数最外侧有花括号,这是不可省略的!

整体上swapcode宏函数的写法:

cpp 复制代码
#define swapcode(...) { ... }

细节: 替换后,外层有花括号

如果去掉花括号,编译以下代码:

cpp 复制代码
#include <stddef.h>
//省略swapcode的定义
int main()
{
    int arr1[]={1,2,3};
    int arr2[]={4,5,6};
    int pi=0;
    int pj=0;
    swapcode(int,arr1,arr2,sizeof(arr1));
    return 0;
}

gcc直接报错:

自己定义的pi、pj是int类型,而宏替换后pi、pj又被定义为int*类型,于是gcc报类型冲突(conflicting types)的问题,而加上花括号不存在这样的问题

题外话: 补充do-while(0)的大括号作用

swapcode中用到了do-while,这让我想起了do-while在宏中能充当大括号作用,例如linux 6.18.4内核的/include/linux/printk.h中这样一段代码:

cpp 复制代码
/**
 * printk_cpu_sync_put_irqrestore() - Release the printk cpu-reentrant spinning
 *                                    lock and restore interrupts.
 * @flags: Caller's saved interrupt state, from printk_cpu_sync_get_irqsave().
 */
#define printk_cpu_sync_put_irqrestore(flags)	\
	do {					\
		__printk_cpu_sync_put();	\
		local_irq_restore(flags);	\
	} while (0)

这里不需要理解printk_cpu_sync_put_irqrestore具体是干什么的,只需要知道do {...}while(0)这个结构,其能充当大括号使用

例题

设计一个my_marco_func()宏函数,执行以下代码:

cpp 复制代码
int main()
{
    if (1)
        my_marco_func();
    else
        printf("333\n");
}

注意: do-while循环在C语言中必须以分号结束

要求打印结果为:

代码1(错误)

可能有读者会这样想: 宏函数一次性需要执行两个printf,直接用花括号不就行了嘛

cpp 复制代码
#include <stdio.h>
#define my_marco_func() { \
                    printf("111\n"); \
                    printf("222\n"); \
                 }
int main()
{
    if (1)
        my_marco_func();
    else
        printf("333\n");
}

但这样会引发一个语法错误: 悬垂else,也叫悬空else (讲解在21.【C语言】顺序结构和选择结构之if文章)

gcc报错如下:

看看预处理文件:

那把my_marco_func()结尾的;去除不就行了?

cpp 复制代码
    if (1)
        my_marco_func() //去除;
    else
        printf("333\n");

运行结果:虽然可以,但不建议去除,这样不符合函数调用的习惯

代码2(错误)

如果直接替换为printf,而不加花括号,也会引发悬垂else的问题,这里省略分析

cpp 复制代码
#include <stdio.h>
#define my_marco_func() { \
                    printf("111\n"); \
                    printf("222\n"); 
                 
int main()
{
    if (1)
        my_marco_func()
    else
        printf("333\n");
}

运行结果:

代码3(正确)
cpp 复制代码
#include <stdio.h>
#define my_marco_func() do \
                 { \
                    printf("111\n"); \
                    printf("222\n"); \
                 } \
                 while (0);
int main()
{
    if (1)
        my_marco_func();
    else
        printf("333\n");
}

运行结果:

看看预处理文件:

剩余部分代码,下一篇文章再分析

相关推荐
hanqunfeng2 小时前
(一)Redis 7 + ACL 单节点、主从、哨兵、集群构建方法
redis
l1t2 小时前
利用豆包辅助编写数独隐式唯一数填充c程序
c语言·开发语言·人工智能·算法·豆包·deepseek
茁壮成长的露露2 小时前
MongoDB单机安装
数据库·mongodb
qq_406176142 小时前
JS防抖与节流:从原理到实战的性能优化方案
服务器·数据库·php
a***59262 小时前
MySQL数据可视化实战指南
数据库·mysql·信息可视化
Maggie_ssss_supp2 小时前
LINUX-MySQL多表查询
数据库·mysql
lxp1997412 小时前
Mysql短课题全手稿
数据库·mysql
我是一只小青蛙8882 小时前
Python实战:Kingbase数据库高效操作指南
数据库·oracle
龙亘川2 小时前
【课程5.7】代码编写:违建处置指标计算(违建发现率、整改率SQL实现)
数据库·oracle·智慧城市·一网统管平台