Appearance
string.c
#include "types.h"
/**
* @brief 将指定内存区域的前 n 个字节设置为指定的值。
* @param dst 指向要填充的内存区域的指针。
* @param c 要设置的值。该值以 int 类型传入,但函数在填充内存时会使用其无符号字符的表示。
* @param n 要设置的字节数。
* @return 返回一个指向内存区域 dst 的指针。
*/
void*
memset(void *dst, int c, uint n)
{TIP
将通用指针 dst 转换为字符指针 cdst,以便按字节进行操作。
char *cdst = (char *) dst;
int i;TIP
循环 n 次,将每个字节都设置为 c。
for(i = 0; i < n; i++){
cdst[i] = c;
}TIP
返回原始指针,方便链式调用。
return dst;
}
/**
* @brief 比较两个内存区域的前 n 个字节。
* @param v1 指向第一个内存区域的指针。
* @param v2 指向第二个内存区域的指针。
* @param n 要比较的字节数。
* @return 一个整数,表示比较结果:
* < 0 表示 v1 小于 v2。
* = 0 表示 v1 等于 v2。
* > 0 表示 v1 大于 v2。
*/
int
memcmp(const void *v1, const void *v2, uint n)
{TIP
将通用指针转换为无符号字符指针,以便按字节进行比较。 使用 uchar 是为了确保比较的是正值,避免 char 的符号扩展问题。
const uchar *s1, *s2;
s1 = v1;
s2 = v2;TIP
循环 n 次,逐字节比较。
while(n-- > 0){TIP
如果发现不相等的字节,则返回它们的差值。
if(*s1 != *s2)
return *s1 - *s2;TIP
如果相等,则继续比较下一个字节。
s1++, s2++;
}
TIP
如果 n 个字节都相等,则返回 0。
return 0;
}
/**
* @brief 从源地址 src 复制 n 个字节到目标地址 dst。
* 这个函数能够正确处理源和目标内存区域重叠的情况。
* @param dst 目标内存区域的指针。
* @param src 源内存区域的指针。
* @param n 要复制的字节数。
* @return 返回一个指向目标内存区域 dst 的指针。
*/
void*
memmove(void *dst, const void *src, uint n)
{
const char *s;
char *d;
TIP
如果要复制的字节数为 0,则直接返回。
if(n == 0)
return dst;
s = src;
d = dst;
TIP
关键逻辑:处理内存重叠 当源地址在目标地址之前 (s < d),并且源的末尾 (s+n) 超过了目标的开头 (d) 时, 说明发生了重叠,且从前向后复制会覆盖尚未读取的源数据。 此时必须从后向前复制。
if(s < d && s + n > d){TIP
将指针移动到源和目标区域的末尾。
s += n;
d += n;TIP
从后向前复制 n 个字节。
while(n-- > 0)
*--d = *--s;
} else {TIP
如果没有重叠,或者重叠情况允许从前向后复制 (例如,目标地址在源地址之前),则直接从前向后复制。
while(n-- > 0)
*d++ = *s++;
}
TIP
返回原始目标指针。
return dst;
}
TIP
memcpy 是为了安抚 GCC 编译器而存在的。在 xv6 内核中,我们总是使用 memmove, 因为它更安全,可以处理内存重叠的情况。这是一个很好的内核编程实践。
void*
memcpy(void *dst, const void *src, uint n)
{
return memmove(dst, src, n);
}
/**
* @brief 比较两个字符串的前 n 个字节。
* @param p 指向第一个字符串的指针。
* @param q 指向第二个字符串的指针。
* @param n 要比较的最大字节数。
* @return 一个整数,表示比较结果:
* < 0 表示 p 小于 q。
* = 0 表示 p 等于 q。
* > 0 表示 p 大于 q。
*/
int
strncmp(const char *p, const char *q, uint n)
{TIP
循环直到 n 为 0,或者遇到字符串结束符 '\0',或者发现不相等的字符。
while(n > 0 && *p && *p == *q)
n--, p++, q++;TIP
如果是因为 n 减到了 0 (意味着前 n 个字符都相等),则返回 0。
if(n == 0)
return 0;TIP
否则,返回第一个不相等字符的差值。 转换为 uchar 是为了防止 char 的符号位影响比较结果。
return (uchar)*p - (uchar)*q;
}
/**
* @brief 从字符串 t 复制最多 n 个字符到 s。
* @warning 这个函数存在风险!如果源字符串 t 的长度大于或等于 n,
* 那么复制到 s 的结果将不会以空字符 ('\0') 结尾。
* @param s 目标缓冲区。
* @param t 源字符串。
* @param n 要复制的最大字符数(包括可能的空字符)。
* @return 返回指向目标缓冲区 s 的指针。
*/
char*
strncpy(char *s, const char *t, int n)
{
char *os;
os = s;TIP
复制字符,直到 n 为 0,或者 t 已经到了结尾 ('\0')。 这个表达式相当紧凑:将 *t 赋值给 *s,然后检查这个值是否为 0。
while(n-- > 0 && (*s++ = *t++) != 0)
;TIP
如果 t 已经复制完毕但 n 还有剩余,用空字符填充 s 的剩余部分。 这是 strncpy 的一个特性。
while(n-- > 0)
*s++ = 0;
return os;
}
/**
* @brief 安全地从 t 复制最多 n-1 个字符到 s,并保证结果字符串以空字符 ('\0') 结尾。
* 这是对 strncpy 的一个更安全的替代方案。
* @param s 目标缓冲区。
* @param t 源字符串。
* @param n 目标缓冲区的总大小。
* @return 返回指向目标缓冲区 s 的指针。
*/
char*
safestrcpy(char *s, const char *t, int n)
{
char *os;
os = s;TIP
如果缓冲区大小无效,则直接返回。
if(n <= 0)
return os;TIP
循环复制,但保留一个字节的空间用于存放结尾的空字符。 n-1个字符被复制后循环会停止。
while(--n > 0 && (*s++ = *t++) != 0)
;TIP
在字符串的末尾强制放置空字符,确保字符串正确终止。
*s = 0;
return os;
}
/**
* @brief 计算字符串的长度,不包括结尾的空字符 ('\0')。
* @param s 指向要计算长度的字符串的指针。
* @return 字符串 s 的长度。
*/
int
strlen(const char *s)
{
int n;
TIP
循环遍历字符串,直到遇到空字符 s[n] == '\0'。
for(n = 0; s[n]; n++)
;
return n;
}