字符串函数、内存函数的模拟实现&大小端字节序

一、strncpy函数的模拟实现

下面这段代码是一个简单的 strncpy 函数的实现,它的功能是将源字符串的前n个字符复制到目标字符串中。下面是对这段代码的详细解释:

char* my_strncpy(char* dest, const char* src, size_t n) {
  // 循环读取源字符串
  for (size_t i = 0; i < n && *src != '\0'; i++) {
    // 将读取到的字符拷贝到目标字符串
    *dest++ = *src++;
  }
  // 将目标字符串置为'\0'
  *dest = '\0';
  return dest;
}

这个函数接受三个参数:目标字符串 dest ,源字符串 src 和要复制的字符数 n 。在循环中,它会检查是否已经复制了 n 个字符,或者是否已经到达了源字符串的末尾(即遇到了空字符’\0’)。如果满足任一条件,循环就会停止。在每次循环中,它都会将源字符串的当前字符复制到目标字符串,然后更新源字符串和目标字符串的指针。最后,它会在目标字符串的末尾添加一个空字符’\0’,以确保字符串的正确终止。

int main() {
  char dest[20];
  char src[] = "hello world";
  // 拷贝5个字符
  my_strncpy(dest, src, 5);
  printf("%s\n", dest);
  return 0;
}

在 main 函数中,我们定义了一个目标字符串 dest 和一个源字符串 src 。然后,我们调用 my_strncpy 函数,将源字符串的前5个字符复制到目标字符串。最后,我们打印出目标字符串,结果应该是"hello"。

二、strncat函数的模拟实现

有了strncpy函数的模拟实现思路,strncat 函数的模拟实现思路就可以沿袭,只需要添加一些代码使其在拷贝字符前先抵达目标字符串的起始追加地址(即末尾),思路如下:

char* p = dest;
while (*p)
    p++;

此时p即指向要拷贝进入的起始地址,后续再按strncpy的思路实现即可。

 // 循环读取源字符串
  for (size_t i = 0; i < n && *src != '\0'; i++) {
    // 将读取到的字符拷贝到目标字符串
    *p++ = *src++;
  }

  // 将目标字符串置为'\0'
  *p = '\0';

  return dest;
}

总体一览:

#include <stdio.h>

char* my_strncat(char* dest, const char* src, size_t n) {
  char* p = dest;
  while (*p)
    p++;
  // 循环读取源字符串
  for (size_t i = 0; i < n && *src != '\0'; i++) {
    // 将读取到的字符拷贝到目标字符串
    *p++ = *src++;
  }
  // 将目标字符串置为'\0'
  *p = '\0';
  return dest;
}

int main() 
{
  char dest[20] = "hello";
  char src[] = ", world!";
  // 追加5个字符
  my_strncat(dest, src, 7);
  printf("%s\n", dest);
  return 0;
}

运行结果截图:

三、memcpy函数的模拟实现

在C语言中, memcpy 函数是一个非常常用的内存操作函数,它可以将源内存块的内容复制到目标内存块中。下面是一个简单的 memcpy 函数的模拟实现:

void my_memcpy(void* p1, void* p2, size_t num)
{
	while (num--)
	{
		*(char*)p1 = *(char*)p2;
		p1 = (char*)p1 + 1;
		p2 = (char*)p2 + 1;
	}
}

这个函数接受三个参数:目标内存块 p1 ,源内存块 p2 和要复制的字节数 num 。在循环中,它会将源内存块的当前字节复制到目标内存块,然后更新源内存块和目标内存块的指针。当复制了 num 个字节后,循环就会停止。

下面是一个使用这个函数的例子:

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	int b[] = { 1,1,1,1 };
	my_memcpy(a+5, b, 4 * sizeof(int));
	for (int i = 0; i < 9; i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

在这个例子中,我们定义了两个整数数组 a 和 b 。然后,我们调用 my_memcpy 函数,将数组 b 的内容复制到数组 a 的第6个元素开始的位置。注意,由于我们复制的是整数,所以我们需要将复制的字节数设为 4*sizeof(int) 。最后,我们打印出数组 a 的内容,结果应该是"1 2 3 4 5 1 1 1 1"。

四、memmove函数的模拟实现

在C语言中, memmove 函数是一个非常常用的内存操作函数,它可以将源内存块的内容复制到目标内存块中。与 memcpy 函数不同,memmove 函数可以处理源内存块和目标内存块重叠的情况。下面是一个简单的 memmove 函数的模拟实现:

void* my_memmove(void* dst, void* src, size_t num)
{
	void* ret = dst;
	assert(dst && src);
	if (dst <= src) {
		while (num--) {
			*(char*)dst = *(char*)src;
			dst = (char*)dst + 1;
			src = (char*)src + 1;
		}
	}
	else {
		while (num--) {
			*((char*)dst + num) = *((char*)src + num);
		}
	}
	return ret;
}

这个函数接受三个参数:目标内存块 dst ,源内存块 src 和要复制的字节数 num 。在函数开始时,我们首先检查 dst 和 src 是否为有效的指针。然后,我们根据 dst 和 src 的相对位置来决定复制的方向。所有复制方向的选择如下图所示:

在代码中,如果 dst 位于 src 的前面或者与 src 重叠,我们就从前向后复制。否则,我们就从后向前复制。这样可以确保在源内存块和目标内存块重叠的情况下,复制的结果是正确的。

下面是一个使用这个函数的例子:

int main()
{
	char a[] = "aaaxxxaaa";
	my_memmove(a + 3, a, 6 * sizeof(char));
	printf("%s\n",a);
	return 0;
}

在这个例子中,我们定义了一个字符串 a 。然后,我们调用 my_memmove 函数,将字符串 a 的前6个字符复制到字符串 a 的第4个字符开始的位置。注意,由于我们复制的是字符,所以我们需要将复制的字节数设为 6*sizeof(int) 。最后,我们打印出字符串 a 的内容,结果应该是"aaaaaaxxx"。

五、大小端字节序

在C语言中,当我们在不同的系统或硬件平台上存储和传输多字节数据时,就会涉及到大小端(Endianness)的问题。

1. 什么是大小端字节序?

大小端指的数据在内存中的存储顺序。

大端字节序(Big Endian):数据的高字节存储在内存的低地址端,低字节存储在内存的高地址端。
小端字节序(Little Endian):数据的高字节存储在内存的高地址端,低字节存储在内存的低地址端。

举个例子,整数0x12345678在内存中的存储:
Big Endian:          Little Endian:  

低地址                  高地址
┌────┐            ┌────┐
│12       │            │78       │  
├────┤            ├────┤
│34       │            │56       │
├────┤            ├────┤  
│56       │            │34       │
├────┤            ├────┤
│78       │            │12       │
└────┘            └────┘
高地址                  低地址  

可见,大小端是从数据的最高有效位(MSB)还是最低有效位(LSB)开始存储的问题。

常见的大端系统有Sun SPARC, IBM POWER, MIPS,常见的x86架构则几乎都是小端字节序。

2. 为什么需要区分大小端字节序?

不同系统之间交换数据时,由于大小端的差异可能导致接收数据被错误解释。

例如一个16位的整数0x1234,在大端系统中存为内存地址0x1000存0x12,0x1001存0x34;但在小端系统中,0x1000存0x34,0x1001存0x12。

如果不区分大小端,这两段内存数据就会被不同解释。

所以,为了在不同架构系统之间进行数据交换与转换,我们必须关注及处理好大小端字节序的问题。

3. C语言中如何判断大小端?

下面是一个很巧妙的方法,利用C指针和内存布局的特性来检测系统字节序。

#include <stdio.h>

int main() {
    unsigned int i = 1;
    char *c = (char*)&i;
    if (*c)
        printf("Little endian");
    else
        printf("Big endian");
    return 0;
}

这个代码之所以可以检查系统的字节序,主要利用了以下几点:

1) 将一个整数用char* 指针去解引用,可以访问整数在内存中的字节表示。

2) 在大端系统中,整数的最高有效字节存储在低地址端,而在小端系统中,整数的最低有效字节存储在低地址端。

3) 这里的整数初始化为1,二进制表示是0000 0001。

4) 在大端系统中,解引用char* pointer所得到的字节值为0。在小端系统中,得到的是1。

5) 通过判断解引用的字节值,可以判断系统的大小端。

具体分析:

在大端系统中,整数 1 在内存中是这样存储的:

地址        内容
0x1000    00
0x1001    00 
0x1002    00
0x1003    01

解引用char* 指针,得到的字节值是最高有效字节00。

在小端系统中,整数 1 在内存中是这样存储的: 

地址        内容
0x1000    01  
0x1001    00
0x1002    00
0x1003    00

解引用char* 指针,得到的字节值是最低有效字节01。

所以通过判断第一个字节是否为0,就可以区分系统的大小端。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值