You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MASM环境下DbRecord动态数组addElement函数修复问询

问题描述

我要实现一个存储DbRecord结构体指针的动态数组,arrayPtr指向数组首元素,支持动态添加新元素并维护数组。计划用VirtualAlloc分配内存、RtlMoveMemory复制数据,但addElement函数无法正确将新元素加入数组。初始版本里对RtlMoveMemory的用法存疑,更新版本调整了该函数的参数,但不知道怎么把VirtualAlloc返回的内存地址存入arrayPtr的对应位置(arrayPtr + arrayCount * sizeof DbRecord)。

初始版本代码

.386
.model flat, stdcall
option casemap :none

include windows.inc
include user32.inc
include kernel32.inc

addElement PROTO: ptr DbRecord

.data?
    DbRecord struct
        Id      dd  ?
        WordOne     db 32   dup(?) ; db is define byte, set value of byte
        WordTwo     db 32   dup(?)
        WordThree   db 32   dup(?)
        Year        dd  ?
    DbRecord ends

    arrayPtr dd ?       ; pointer in memory to start of array
    newElementPointer DbRecord <>
    hStdOut dd ?
    bytesWritten dd ?

.data
    arrayCount dd 0
    hello db 'Hello World!', 0

.code
main proc
    LOCAL DbRecord01:DbRecord
    mov [DbRecord01.Id], 1;

    ; any other way than one character at a time?
    mov byte ptr [DbRecord01.WordOne], 'D'
    mov byte ptr [DbRecord01.WordOne + 1], 'o'
    mov byte ptr [DbRecord01.WordOne + 2], 'g'
    mov byte ptr [DbRecord01.WordOne + 3], 0

    mov byte ptr [DbRecord01.WordTwo], 'C'
    mov byte ptr [DbRecord01.WordTwo + 1], 'a'
    mov byte ptr [DbRecord01.WordTwo + 2], 't'
    mov byte ptr [DbRecord01.WordTwo + 3], 0

    mov byte ptr [DbRecord01.WordThree], 'E'
    mov byte ptr [DbRecord01.WordThree + 1], 'y'
    mov byte ptr [DbRecord01.WordThree + 2], 'e'
    mov byte ptr [DbRecord01.WordThree + 3], 0

    mov [DbRecord01.Year], 2022;

    invoke GetStdHandle, STD_OUTPUT_HANDLE
    mov [hStdOut], eax

    invoke WriteConsole, hStdOut, offset hello, sizeof hello, offset bytesWritten, NULL
    invoke addElement, addr DbRecord01

    ret
main endp

addElement proc DbRecordPointer: ptr DbRecord
    invoke VirtualAlloc, NULL, sizeof DbRecord, MEM_COMMIT, PAGE_READWRITE  ; I beleive store a memory address in eax
    invoke RtlMoveMemory, DbRecord ptr [eax], DbRecordPointer, sizeof DbRecord ; but how to use that memory address here?
    ret
addElement endp

end main

更新版本代码

.386
.model flat, stdcall
option casemap :none

include windows.inc
include user32.inc
include kernel32.inc

addElement PROTO: ptr DbRecord

.data?
    DbRecord struct
        Id      dd  ?
        WordOne     db 32   dup(?) ; db is define byte, set value of byte
        WordTwo     db 32   dup(?)
        WordThree   db 32   dup(?)
        Year        dd  ?
    DbRecord ends

    arrayPtr dword ?        ; pointer in memory to start of array
;   newElementPointer DbRecord <>
    hStdOut dd ?
    bytesWritten dd ?

.data
    arrayCount dd 0
    hello db 'Hello World!', 0

.code
main proc
    LOCAL DbRecord01:DbRecord
    mov [DbRecord01.Id], 1;

    ; any other way than one character at a time?
    mov byte ptr [DbRecord01.WordOne], 'D'
    mov byte ptr [DbRecord01.WordOne + 1], 'o'
    mov byte ptr [DbRecord01.WordOne + 2], 'g'
    mov byte ptr [DbRecord01.WordOne + 3], 0

    mov byte ptr [DbRecord01.WordTwo], 'C'
    mov byte ptr [DbRecord01.WordTwo + 1], 'a'
    mov byte ptr [DbRecord01.WordTwo + 2], 't'
    mov byte ptr [DbRecord01.WordTwo + 3], 0

    mov byte ptr [DbRecord01.WordThree], 'E'
    mov byte ptr [DbRecord01.WordThree + 1], 'y'
    mov byte ptr [DbRecord01.WordThree + 2], 'e'
    mov byte ptr [DbRecord01.WordThree + 3], 0

    mov [DbRecord01.Year], 2022;

    invoke GetStdHandle, STD_OUTPUT_HANDLE
    mov [hStdOut], eax

    invoke WriteConsole, hStdOut, offset hello, sizeof hello, offset bytesWritten, NULL
    invoke addElement, addr DbRecord01

    ret
main endp

addElement proc uses edx DbRecordPointer: ptr DbRecord
    Local newElementPointer: Dword
    invoke VirtualAlloc, NULL, sizeof DbRecord, MEM_COMMIT, PAGE_READWRITE  ; I beleive store a memory address in eax
    mov newElementPointer, eax

    ;invoke RtlMoveMemory, newElementPointer , DbRecordPointer, sizeof DbRecord ; but how to use that memory address here?
    invoke RtlMoveMemory, eax , DbRecordPointer, sizeof DbRecord
    mov edx, arrayCount
    inc edx
    mov arrayCount, edx
    ;mov dword ptr [arrayPtr+arrayCount], eax

    ret
addElement endp

end main
解决方案

要实现存储结构体指针的动态数组,核心是先管理数组本身的内存(存储指针的数组),再管理每个结构体实例的内存,具体修正步骤如下:

1. 修正addElement函数逻辑

核心步骤:

  • 计算新数组所需内存:每次添加元素后,数组大小为(arrayCount + 1) * sizeof DWORD(因为每个元素是4字节的指针)
  • 处理数组扩容:
    • 如果是第一次添加元素(arrayPtr为空),直接分配新数组内存
    • 如果已有数组,重新分配更大的内存,复制旧数组内容,释放旧内存
  • 分配新的DbRecord内存并复制数据
  • 将新结构体的指针存入数组的对应位置
  • 更新数组元素计数

修正后的addElement代码

addElement proc uses ebx ecx edx DbRecordPointer: ptr DbRecord
    LOCAL newArrayPtr: DWORD
    LOCAL newRecordPtr: DWORD

    ; 1. 分配新的DbRecord内存并复制数据
    invoke VirtualAlloc, NULL, sizeof DbRecord, MEM_COMMIT, PAGE_READWRITE
    .if eax == NULL
        ret ; 内存分配失败,直接返回
    .endif
    mov newRecordPtr, eax
    invoke RtlMoveMemory, newRecordPtr, DbRecordPointer, sizeof DbRecord

    ; 2. 处理数组的扩容或初始化
    mov ecx, arrayCount
    inc ecx ; 新的元素数量
    mov edx, sizeof DWORD
    mul edx ; eax = 新数组所需字节数 (ecx * 4)

    .if arrayPtr == NULL
        ; 第一次添加,直接分配数组内存
        invoke VirtualAlloc, NULL, eax, MEM_COMMIT, PAGE_READWRITE
        .if eax == NULL
            invoke VirtualFree, newRecordPtr, 0, MEM_RELEASE ; 释放已分配的结构体内存
            ret
        .endif
        mov newArrayPtr, eax
    .else
        ; 已有数组,重新分配更大的内存
        invoke VirtualAlloc, NULL, eax, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE
        .if eax == NULL
            invoke VirtualFree, newRecordPtr, 0, MEM_RELEASE
            ret
        .endif
        mov newArrayPtr, eax
        ; 复制旧数组内容到新数组
        mov edx, arrayCount
        mov ecx, sizeof DWORD
        mul edx ; eax = 旧数组字节数
        invoke RtlMoveMemory, newArrayPtr, arrayPtr, eax
        ; 释放旧数组内存
        invoke VirtualFree, arrayPtr, 0, MEM_RELEASE
    .endif

    ; 3. 将新结构体指针存入数组的对应位置
    mov ebx, arrayCount
    mov edx, sizeof DWORD
    mul ebx ; eax = 偏移量 (arrayCount * 4)
    mov [newArrayPtr + eax], newRecordPtr

    ; 4. 更新数组指针和计数
    mov arrayPtr, newArrayPtr
    mov arrayCount, ecx

    ret
addElement endp

2. 额外优化:字符串赋值简化

原代码中逐个字节赋值字符串的方式可以用lstrcpy替换,更简洁:

invoke lstrcpy, addr DbRecord01.WordOne, addr 'Dog'
invoke lstrcpy, addr DbRecord01.WordTwo, addr 'Cat'
invoke lstrcpy, addr DbRecord01.WordThree, addr 'Eye'

关键注意事项

  • 每次内存分配后必须检查返回值是否为NULL,避免空指针访问
  • 数组扩容时要记得释放旧内存,防止内存泄漏
  • 因为数组存储的是结构体指针,所以数组的每个元素是DWORD类型,偏移量计算要用arrayCount * sizeof DWORD,而不是sizeof DbRecord

内容的提问来源于stack exchange,提问作者Chad Botting

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 07:55:17