为什么在C/C++/rtl中没有类似LDIR功能的Z80?

Evi*_*ach 5 c c++ z80

在Z80机器代码中,一种将缓冲区初始化为固定值的廉价技术,比如所有空白.所以一大堆代码可能看起来像这样.

LD HL, DESTINATION             ; point to the source
LD DE, DESTINATION + 1         ; point to the destination
LD BC, DESTINATION_SIZE - 1    ; copying this many bytes
LD (HL), 0X20                  ; put a seed space in the first position
LDIR                           ; move 1 to 2, 2 to 3...
Run Code Online (Sandbox Code Playgroud)

结果是DESTINATION的内存块完全填满了空白.我已经尝试了memmove和memcpy,并且无法复制这种行为.我希望memmove能够正确地完成它.

memmove和memcpy为什么会这样?

有没有合理的方法来进行这种数组初始化?

我已经知道char array [size] = {0}用于数组初始化

我已经知道memset将为单个字符完成工作.

还有什么其他方法可以解决这个问题?

Ned*_*der 12

memmove并且memcpy不要那样工作,因为它不是用于移动或复制内存的有用语义.在Z80中能够填充内存很方便,但为什么你会期望一个名为"memmove"的函数用单个字节填充内存?这是为了移动内存块.无论块如何重叠,它都是为了得到正确的答案(源字节移动到目的地)而实现的.它有助于为移动内存块获得正确的答案.

如果你想填充内存,请使用memset,它可以满足您的需求.


dev*_*fix 11

使用堆栈可以更快地消隐内存区域.尽管LDI和LDIR的使用非常普遍,但David Webb(以各种方式推动ZX Spectrum,如包括边界在内的全屏幕倒计时)提出了这种技术,速度提高了4倍:

  • 保存堆栈指针,然后将其移动到屏幕的末尾.
  • 将HL寄存器对加载为零,
  • 进入一个巨大的循环推动HL进入堆栈.
  • 堆栈向上移动屏幕并向下移动通过内存,在此过程中,清除屏幕.

上面的解释取自David Webbs游戏Starion评论.

Z80例程可能看起来像这样:

  DI              ; disable interrupts which would write to the stack.
  LD HL, 0
  ADD HL, SP      ; save stack pointer
  EX DE, HL       ; in DE register
  LD HL, 0
  LD C, 0x18      ; Screen size in pages
  LD SP, 0x4000   ; End of screen
PAGE_LOOP:
  LD B, 128       ; inner loop iterates 128 times
LOOP:
  PUSH HL         ; effectively *--SP = 0; *--SP = 0;
  DJNZ LOOP       ; loop for 256 bytes
  DEC C
  JP NZ,PAGE_LOOP
  EX DE, HL
  LD SP, HL       ; restore stack pointer
  EI              ; re-enable interrupts
Run Code Online (Sandbox Code Playgroud)

但是,这个例程的速度要快两倍.LDIR每21个周期复制一个字节.内循环每24个循环复制两个字节 - 11个循环PUSH HL,13 个循环DJNZ LOOP.要获得近4倍的速度,只需展开内循环:

LOOP:
   PUSH HL
   PUSH HL
   ...
   PUSH HL         ; repeat 128 times
   DEC C
   JP NZ,LOOP
Run Code Online (Sandbox Code Playgroud)

这是每两个字节非常接近11个周期,比每个LDIR字节的21个周期快约3.8倍.

毫无疑问,这项技术已被多次重新发明.例如,它出现在1980年的子逻辑的飞行模拟器1中,用于TRS-80.

  • 16位DEC不设置任何标志,但8位DEC确实如此.将循环重写为C上的内循环和B上的外循环将解决该问题,因为使用比II B更快的IIRC的DJNZ; JNZ LOOP分开.当然,这需要内环超过B ...... (3认同)
  • 我使用的更快的方法是在循环中放入几个"PUSH HL"指令.因此,如果您正在清除2K内存,则可以使用16"PUSH HL"并且仅循环2K/16(256)次. (2认同)

Ono*_*cci 8

我相信这符合C和C++的设计理念.正如Bjarne的Stroustrup的一次的C++设计的主要指导原则之一就是"你不要用什么,你不支付".而丹尼斯里奇可能没有用完全相同的词语说出来,我相信这也是一个指导原则,告知他的C设计(以及后来人的C设计).现在您可能会认为如果您分配内存,它应该自动初始化为NULL,我倾向于同意您的看法.但这需要机器周期,如果你在每个周期都很关键的情况下进行编码,这可能不是一个可接受的权衡.基本上C和C++试图避开你的方式 - 因此如果你想要初始化的东西,你必须自己做.


Kon*_*lph 6

memmove和memcpy为什么会这样?

可能是因为没有针对Z80硬件的特定的现代C++编译器?写一个.;-)

这些语言没有指定给定硬件如何实现任何东西.这完全取决于编译器和库的程序员.当然,为每个可以想象的硬件配置编写一个自己的,高度指定的版本是很多工作.那就是原因.

有没有合理的方法来进行这种数组初始化?有没有合理的方法来进行这种数组初始化?

好吧,如果一切都失败了,你总是可以使用内联汇编.除此之外,我期望std::fill在良好的STL实施中表现最佳.是的,我完全清楚我的期望太高,而且std::memset在实践中往往表现更好.


Eva*_*ran 6

这可以在 x86 汇编中同样轻松地完成。事实上,它归结为与您的示例几乎相同的代码。

mov esi, source    ; set esi to be the source
lea edi, [esi + 1] ; set edi to be the source + 1
mov byte [esi], 0  ; initialize the first byte with the "seed"
mov ecx, 100h      ; set ecx to the size of the buffer
rep movsb          ; do the fill
Run Code Online (Sandbox Code Playgroud)

然而,如果可以的话,一次设置多个字节会更有效。

最后,memcpy/memmove不是您要寻找的,它们用于将内存块从一个区域复制到另一个区域(memmove 允许源和目标成为同一缓冲区的一部分)。memset用您选择的字节填充一个块。

  • 是的,我有这种预感。我的目标是OP的观点而不是你的:) (2认同)

Mar*_*som 5

您展示的Z80序列是最快的方式 - 1978年.那是30年前.从那时起,处理器已经取得了很大进展,而今天这是最慢的方式.

Memmove设计为在源和目标范围重叠时工作,因此您可以将一块内存向上移动一个字节.这是C和C++标准指定行为的一部分.Memcpy未指定; 它可能与memmove完全相同,或者可能有所不同,具体取决于编译器决定如何实现它.编译器可以自由选择比memmove更有效的方法.