文章目录
- 内存函数
- 数据在内存中的存储
-
- 一、整数在内存中的存储
-
- [1.1 原码、反码、补码的定义](#1.1 原码、反码、补码的定义)
- [1.2 为什么使用补码?](#1.2 为什么使用补码?)
- 二、大小端字节序和字节序判断
- 三、浮点数在内存中的存储
- 四、经典练习解析
在C语言中,内存操作是编程的核心环节之一。本文将详细介绍四个常用的内存函数: memcpy
、 memmove
、 memset
和 memcmp
,包括它们的使用方法、模拟实现以及适用场景,帮助你更好地理解和运用这些函数进行内存操作。
内存函数
一、memcpy:内存复制函数
memcpy
函数用于从源内存地址向目标内存地址复制指定字节数的数据,其函数原型如下:
c
void * memcpy ( void * destination, const void * source, size_t num );
函数特点
- 从
source
指向的内存位置开始,复制num
个字节到destination
指向的内存位置。 - 不会因遇到
\0
而停止复制,严格按照指定的字节数操作。 - 不处理重叠内存:如果源地址和目标地址的内存区域有重叠,复制结果是未定义的。
使用示例
c
#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元素,每个int占4字节)
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;
}
模拟实现
c
#include <assert.h>
void * memcpy(void * dst, const void * src, size_t count) {
void * ret = dst;
assert(dst && src); // 确保指针非空
// 按字节逐个复制
while (count--) {
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
return ret;
}
说明 :通过将指针强制转换为char*
,实现按字节操作,确保对任意类型的内存都能正确复制。
二、memmove:处理重叠内存的复制函数
memmove
与memcpy
功能类似,但它支持源内存和目标内存重叠的场景,函数原型如下:
c
void * memmove ( void * destination, const void * source, size_t num );
函数特点
- 与
memcpy
的核心区别:可以安全处理重叠的内存区域。 - 当源地址和目标地址重叠时,使用
memmove
可保证复制结果正确。
使用示例
c
#include <stdio.h>
#include <string.h>
int main() {
int arr1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 将arr1的前5个元素(20字节)复制到arr1+2的位置(从第3个元素开始)
memmove(arr1 + 2, arr1, 20);
for (int i = 0; i < 10; i++) {
printf("%d ", arr1[i]); // 输出:1 1 2 3 4 5 6 7 8 9 10(原文档此处代码笔误,arr2应为arr1)
}
return 0;
}
模拟实现
c
#include <assert.h>
void * memmove(void * dst, const void * src, size_t count) {
void * ret = dst;
assert(dst && src);
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;
}
说明:通过判断内存区域是否重叠,选择正向复制(低到高)或反向复制(高到低),避免覆盖未复制的源数据。
三、memset:内存设置函数
memset
用于将指定内存区域的每个字节设置为指定值,函数原型如下:
c
void * memset ( void * ptr, int value, size_t num );
函数特点
- 以字节为单位 设置内存值,
value
会被转换为unsigned char
后填充。 - 常用于初始化数组或清空内存区域。
使用示例
c
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "hello world";
// 将前6个字节设置为'x'
memset(str, 'x', 6);
printf(str); // 输出:xxxxxxworld
return 0;
}
注意 :memset
按字节操作,若用于设置int
数组,可能无法达到预期效果(如memset(arr, 1, 4)
会将int
值设为0x01010101
,而非1
)。
四、memcmp:内存比较函数
memcmp
用于比较两个内存区域的前num
个字节,函数原型如下:
c
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
函数特点
- 按字节比较,每个字节视为
unsigned char
类型。 - 返回值表示两个内存区域的大小关系:
<0
:ptr1
的第一个不同字节小于ptr2
的对应字节;0
:两个内存区域的前num
个字节完全相同;>0
:ptr1
的第一个不同字节大于ptr2
的对应字节。
使用示例
c
#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("'%s' is greater than '%s'\n", buffer1, buffer2);
else if (n < 0)
printf("'%s' is less than '%s'\n", buffer1, buffer2);
else
printf("'%s' is the same as '%s'\n", buffer1, buffer2);
return 0;
}
说明 :示例中因小写字母的ASCII值大于大写字母,buffer1
会被判定为大于buffer2
。
总结
函数 | 功能 | 特点 | 适用场景 |
---|---|---|---|
memcpy |
复制内存 | 不处理重叠内存,效率较高 | 非重叠内存的复制 |
memmove |
复制内存 | 处理重叠内存,安全性高 | 可能重叠的内存复制 |
memset |
设置内存值 | 按字节操作,用于初始化或填充 | 内存初始化、批量设置值 |
memcmp |
比较内存 | 按字节比较,返回差值关系 | 任意类型内存的比较 |
数据在内存中的存储
一、整数在内存中的存储
整数在内存中以补码形式存储,这是由计算机系统的特性决定的。要理解补码,需先掌握原码、反码的概念:
1.1 原码、反码、补码的定义
- 符号位:最高位为符号位,0表示正数,1表示负数。
- 数值位:剩余位表示数值大小。
- 正整数 :原码、反码、补码完全相同。
- 示例:
int a = 5
(32位)
原码:00000000 00000000 00000000 00000101
反码:00000000 00000000 00000000 00000101
补码:00000000 00000000 00000000 00000101
- 示例:
- 负整数 :
- 原码:直接翻译二进制(符号位为1)。
- 反码:符号位不变,数值位按位取反。
- 补码:反码 + 1。
- 示例:
int b = -5
(32位)
原码:10000000 00000000 00000000 00000101
反码:11111111 11111111 11111111 11111010
补码:11111111 11111111 11111111 11111011
1.2 为什么使用补码?
- 统一符号位和数值位:补码让符号位参与运算,无需额外处理。
- 简化运算 :CPU只有加法器,补码可将减法转换为加法(如
a - b = a + (-b)
)。 - 节省硬件:补码与原码的转换规则统一,无需额外电路。
二、大小端字节序和字节序判断
当数据占多个字节时,会涉及字节在内存中的排列顺序,即字节序。
2.1 大小端的定义
- 大端字节序 :数据的高位字节存于内存低地址,低位字节存于高地址。
- 示例:
0x11223344
存储为11 22 33 44
(低地址到高地址)。
- 示例:
- 小端字节序 :数据的低位字节存于内存低地址,高位字节存于高地址。
- 示例:
0x11223344
存储为44 33 22 11
(低地址到高地址)。
- 示例:
2.2 为什么存在大小端?
计算机以字节为内存单位,对于超过1字节的数据(如short
、int
),不同硬件架构对字节排列顺序有不同约定:
- X86、ARM(默认)等架构采用小端。
- KEIL C51、部分ARM配置采用大端。
2.3 如何判断机器的字节序?
通过代码可快速判断当前系统的字节序:
方法1:指针法
c
#include <stdio.h>
int check_sys() {
int i = 1;
// 取i的地址并强制转换为char*,仅访问第一个字节
return *(char*)&i;
}
int main() {
if (check_sys() == 1) {
printf("小端\n"); // 小端中第一个字节为0x01
} else {
printf("大端\n"); // 大端中第一个字节为0x00
}
return 0;
}
方法2:联合体法
c
#include <stdio.h>
int check_sys() {
union {
int i;
char c;
} un;
un.i = 1;
return un.c; // 联合体成员共用内存,c访问第一个字节
}
三、浮点数在内存中的存储
浮点数(float
、double
)的存储方式与整数完全不同,遵循IEEE 754标准。
3.1 IEEE 754标准格式
任意二进制浮点数V可表示为:
V = (-1)\^S \\times M \\times 2\^E
- S:符号位(0为正,1为负)。
- M:有效数字(1 ≤ M < 2)。
- E:指数位(整数)。
3.2 存储结构
- 32位float:1位S + 8位E + 23位M。
- 64位double:1位S + 11位E + 52位M。
3.3 存储与读取规则
存储过程:
- 规范化M :M默认省略整数位1,仅存小数部分(如
1.001
存为001
),节省1位空间。 - 调整E :E为无符号整数,存储时需加上中间值(float加127,double加1023)。
示例:E=3(float)→ 存储为3+127=130(二进制10000010
)。
读取过程:
- E不全为0且不全为1:E真实值 = 存储值 - 中间值,M前补1。
- E全为0:E真实值 = 1 - 中间值,M前补0(表示接近0的小数)。
- E全为1:M全0表示±无穷大,M非0表示NaN(非数值)。
3.4 示例解析
c
#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
*pFloat = 9.0;
printf("n = %d\n", n); // 输出:1091567616
printf("*pFloat = %f\n", *pFloat); // 输出:9.000000
return 0;
}
- 第一步 :整数9的补码为
00000000 00000000 00000000 00001001
,按float解读时E全为0,结果接近0。 - 第二步 :9.0的float存储为
0 10000010 00100000000000000000000
,按整数解读时为1091567616。
四、经典练习解析
练习1:字符类型的符号影响
c
#include <stdio.h>
int main() {
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a=%d, b=%d, c=%d", a, b, c);
// 输出:a=-1, b=-1, c=255
return 0;
}
char
和signed char
:-1的补码为11111111
,打印时符号扩展为11111111 11111111 11111111 11111111
(仍为-1)。unsigned char
:-1的补码为11111111
,无符号解读为255。
练习2:循环陷阱
c
#include <stdio.h>
unsigned char i = 0;
int main() {
for (i = 0; i <= 255; i++) {
printf("hello world\n"); // 无限循环
}
return 0;
}
unsigned char
范围为0~255,i++后始终≤255,循环永不结束。