String 字符串处理子程序中的字符串缓冲区

String 字符串处理子程序中的字符串缓冲区,string,assembly,mips,String,Assembly,Mips,我目前正在使用mips汇编语言创建一些字符串处理子例程,但遇到了一个问题。 每个子例程都将输出地址作为参数,该参数用作写入新处理字符串的内存位置。起初,我让子程序将字节直接写入输出地址,但很快就意识到应该改为写入临时字符串缓冲区,对其进行处理,然后将缓冲区内容复制到输出地址。 问题是,我该怎么做? 使用.space在main.asm中声明全局静态字符串缓冲区是一种解决方案,但当一个字符串处理子例程被另一个调用并开始覆盖调用方的字符串数据时,可能会出现问题。使用运行时堆栈存储字符串数据是另一种解决

我目前正在使用mips汇编语言创建一些字符串处理子例程,但遇到了一个问题。 每个子例程都将输出地址作为参数,该参数用作写入新处理字符串的内存位置。起初,我让子程序将字节直接写入输出地址,但很快就意识到应该改为写入临时字符串缓冲区,对其进行处理,然后将缓冲区内容复制到输出地址。 问题是,我该怎么做? 使用.space在main.asm中声明全局静态字符串缓冲区是一种解决方案,但当一个字符串处理子例程被另一个调用并开始覆盖调用方的字符串数据时,可能会出现问题。使用运行时堆栈存储字符串数据是另一种解决方案,但也有自己的问题

这是一个普遍的问题吗?处理这种情况的最佳做法是什么


如有任何建议,将不胜感激。谢谢。

复制很慢,请尽可能避免复制。

您有两个很好的选择:

  • 复制和计算:例如,从输出缓冲区末尾开始的字符串反转函数,在向前读取的同时向后写入。反之亦然,因为输入的结尾可能已经在缓存中处于热状态。您希望让输出的开头在缓存中保持热状态,因此最后写入它对于中到大的字符串是很好的。如果有字节交换指令,并且输入和输出都具有必要的对齐方式,则可以轻松地一次完成整字(或SIMD向量)
  • LI>就位:例如用开始和结束的指针开始字符串反转,并将它们相互对置,直到它们在中间交叉。一次超过1个字节需要进行检查,以避免交叉时重叠。(虽然如果您将自己限制在单词对齐的加载/存储上,那么您已经必须处理这个问题,并且根据对齐+长度的不同,快速加载可能并不容易。)
如果您使用的是隐式长度字符串,并且必须
strlen
输入,那么输入的结尾在缓存中已经很热,您应该从那里开始读取。有些算法你可以在知道输出需要多长时间之前就开始写输出,所以如果你需要先strlen来找出你需要的scratch数组有多大,情况会更糟

您甚至可以在不同的函数中实现某些操作的复制版本和就地版本,以便为调用者提供选项,这取决于调用者是否需要保留原始版本。如果对性能的关注足以在asm中手工编写,请设计API以避免不必要的复制

(对于某些算法,复制和就地可以使用相同的代码。)

通常,您只需处理到输出缓冲区。

您可以指定(在注释/文档/带有
char*f
arg的C函数签名中)您的函数在读取其所有输入之前写入其输出,因此输入和输出重叠不一定有效

如果支持精确就位(例如,对于每个字母输入字符都大写的函数,其中,
out[i]
仅依赖于
in[i]
),则您也可以记录该函数,即使部分重叠不会。(例如,如果将
out[i]
写入[i+某物]会破坏

这样的函数可以像不重叠的副本和upcase一样准确地工作


如果出于某种原因确实需要tmp缓冲区

  • 如果您的tmp缓冲区大小较小且大小固定(不随输入字符串长度缩放),请在调用堆栈上分配它

  • 将问题推给调用者:要求调用者提供适当大小的tmp缓冲区作为额外参数。然后,您的呼叫方可以跨多个呼叫重用此空间,只要其足够大,可以容纳最大的被呼叫方

调用方可能知道它不需要是可重入的或线程安全的,并且可以使用静态缓冲区。或者它可能知道最大大小足够小,可以安全地使用堆栈,而不会出现堆栈溢出的风险

当然,对于大型字符串,动态分配+免费可能是最坏情况下的回退。在像MARS或SPIM这样的玩具操作系统上,我认为有一个
syscall
。在真正的MIPS上,通常会有某种
malloc
库函数,您可以与维护空闲列表的优化分配器一起使用。您希望避免进行实际的系统调用来获取和释放一个小的缓冲区


您甚至可以在运行时检查大小是否较小,并决定使用堆栈空间(也可以在函数末尾进行分支,以决定是释放堆栈指针还是调整堆栈指针)。

除非需要适应大量字符串,否则请使用堆栈。否则,请使用堆(即使用类似
malloc
的方式分配内存)。