movedata 是一个在 16位实模式(例如早期的 MS-DOS 环境)下使用的、用于在内存块之间快速复制数据的函数,它在现代的 32位或 64位保护模式操作系统(如 Windows, Linux, macOS)中已经不再使用,并且如果强行使用会导致程序崩溃。

函数原型
movedata 函数通常在 <dos.h> 或 <mem.h> 头文件中声明。
void movedata(unsigned srcseg, unsigned srcoff, unsigned destseg, unsigned destoff, unsigned numbytes);
参数详解
movedata 的独特之处在于它不使用普通的指针(如 void*),而是使用段:偏移地址来定位内存,这是由 16位 x86 架构的内存分段机制决定的。
unsigned srcseg: 源数据的段地址,这是一个 16位的值,表示内存块的起始段。unsigned srcoff: 源数据的偏移地址,这是一个 16位的值,表示在段内的具体位置。unsigned destseg: 目标数据的段地址,内存块要复制到的起始段。unsigned destoff: 目标数据的偏移地址,在目标段内的具体位置。unsigned numbytes: 要复制的字节数。
内存地址计算方式:
实际的物理地址 = 段地址 * 16 + 偏移地址。
段地址为 0x1000,偏移地址为 0x0050 的物理地址是 0x1000 * 16 + 0x0050 = 0x10050。
功能
该函数的功能是:从 srcseg:srcoff 指定的内存位置,复制 numbytes 个字节的数据到 destseg:destoff 指定的内存位置。

它的一个重要特性是:它可以跨段复制数据,并且使用了底层指令,速度比用循环逐字节复制要快得多。
使用示例 (DOS 环境)
这是一个在 Turbo C 或 Borland C++ 等 16位编译器下运行的 DOS 程序示例,它会将一块内存中的数据复制到另一块内存中,然后打印出来。
#include <stdio.h>
#include <dos.h> // 包含 movedata 的声明
int main() {
// 定义源和目标的段:偏移地址
unsigned source_segment = 0x1000; // 假设源数据在 0x1000 段
unsigned source_offset = 0x0000; // 从段的开头开始
unsigned dest_segment = 0x2000; // 假设目标在 0x2000 段
unsigned dest_offset = 0x0000; // 从段的开头开始
// 要复制的字节数
unsigned num_bytes = 10;
// 1. 在源内存块中写入一些数据
// 注意:在实模式下,直接通过段:偏移访问内存需要特殊处理或汇编
// 这里我们用 movedata 的“反向操作”来初始化源数据,以展示其用法
char init_data[] = "Hello, DOS!";
// 将初始化数据从代码段(由 FP_SEG 获取)复制到源内存块
movedata(FP_SEG(init_data), FP_OFF(init_data), source_segment, source_offset, sizeof(init_data));
printf("Data has been moved from segment 0x%04X to segment 0x%04X\n", source_segment, dest_segment);
// 2. 将数据从源内存块复制到目标内存块
movedata(source_segment, source_offset, dest_segment, dest_offset, num_bytes);
// 3. 从目标内存块读取并打印数据
char buffer[20];
movedata(dest_segment, dest_offset, FP_SEG(buffer), FP_OFF(buffer), num_bytes);
buffer[num_bytes] = '\0'; // 确保字符串正确终止
printf("Data in destination buffer: %s\n", buffer);
return 0;
}
如何编译和运行 (仅适用于DOS环境): 你需要在支持 16位 DOS 代码的编译器(如 Turbo C++ 3.0)中编译此代码,并在 DOS 模拟器(如 DOSBox)中运行。
重要警告:为什么在现代 C/C++ 中不能使用?
movedata 是一个过时且危险的函数,在现代编程中必须避免使用,原因如下:

-
实模式 vs. 保护模式:
- DOS (实模式):内存是平坦的,程序可以访问任何内存地址(
0x00000到0xFFFFF),movedata就是在这个模型下工作的。 - 现代操作系统 (保护模式):内存是虚拟和受保护的,每个进程都有自己的、独立的虚拟地址空间(在 32位系统上是 4GB),操作系统通过内存管理单元和页表将虚拟地址映射到物理内存。
movedata使用的seg:off地址是物理地址,在保护模式下,你的程序根本不知道物理地址是什么,也无法直接访问它们,试图使用movedata会传递一个无效的段地址,导致 CPU 触发一个通用保护故障,操作系统会立即终止你的程序。
- DOS (实模式):内存是平坦的,程序可以访问任何内存地址(
-
内存管理:
- 在现代 C/C++ 中,你通过
malloc,new,calloc等函数向操作系统申请内存块,操作系统返回给你的是一个虚拟地址指针(如void*或char*)。 - 你应该使用标准库函数如
memcpy来在这些由malloc分配的内存块之间复制数据。
- 在现代 C/C++ 中,你通过
-
安全性:
movedata可以复制任何地方的内存,包括操作系统的内核空间,极其危险。memcpy是类型安全的,它接受void*指针,并且其作用范围严格限定在进程自己的虚拟地址空间内,由编译器和操作系统共同管理,安全得多。
现代替代方案:memcpy
在所有现代 C/C++ 编程中,你应该使用 memcpy 函数。
memcpy 的原型:
#include <string.h> // 包含 memcpy 的声明 void *memcpy(void *dest, const void *src, size_t num);
memcpy 的特点:
- 安全:在进程自己的虚拟地址空间内操作。
- 高效:编译器通常会将其优化为高效的机器指令(如
rep movsb)。 - 标准:是 C/C++ 标准库的一部分,可移植性极强。
现代 C/C++ 编程示例:
#include <stdio.h>
#include <string.h> // 使用 memcpy
#include <stdlib.h> // 使用 malloc
int main() {
// 1. 分配源内存和目标内存
char *src_buffer = (char*)malloc(100);
char *dest_buffer = (char*)malloc(100);
if (src_buffer == NULL || dest_buffer == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// 2. 在源内存中写入数据
strcpy(src_buffer, "This is a modern way to copy data.");
// 3. 使用 memcpy 复制数据
// dest_buffer: 目标指针
// src_buffer: 源指针
// 50: 要复制的字节数
memcpy(dest_buffer, src_buffer, 50);
// 4. 打印结果
printf("Source buffer: %s\n", src_buffer);
printf("Destination buffer (first 50 bytes): %s\n", dest_buffer);
// 5. 释放内存
free(src_buffer);
free(dest_buffer);
return 0;
}
| 特性 | movedata |
memcpy |
|---|---|---|
| 时代 | 过时,用于 16位 DOS 实模式 | 现代标准,用于所有保护模式系统 |
| 地址模型 | 物理地址 (seg:off) |
虚拟地址 (void*) |
| 头文件 | <dos.h>, <mem.h> |
<string.h> |
| 安全性 | 极不安全,可访问任意物理内存 | 安全,在进程虚拟空间内操作 |
| 可移植性 | 差,仅限 x86 架构的 DOS | 极佳,所有 C/C++ 平台都支持 |
| 推荐使用 | 绝对不要在新的代码中使用 | 必须使用,是复制内存块的标准方法 |
movedata 是一个历史遗迹,了解它有助于理解计算机发展史,但在任何现代软件开发项目中,都应该使用 memcpy 来完成内存块复制的任务。
