【问题标题】:Nim sequence assignment value semanticsNim 序列赋值语义
【发布时间】:2022-10-05 05:46:18
【问题描述】:

我的印象是序列和字符串总是在赋值时被深度复制。今天,当我与一个 C 库交互时,我被烧毁了,我将 Nim 序列的 unsafeAddr 传递给该库。 C 库从传递的指针开始写入内存区域。

由于我不希望库更改原始 Nim 序列,因此我想通过将序列分配给名为 copy 的新变量来简单地复制序列,并将副本的地址传递给库。

瞧,这些修改仍然出现在最初的 Nim 序列中。更奇怪的是,这种行为取决于副本是通过let copy = ...(确实显示更改)还是通过var copy = ...(更改不显示)声明的。 以下代码在一个非常简化的 Nim 示例中演示了这一点:

proc changeArgDespiteCopyAssignment(x: seq[int], val: int): seq[int] =
  let copy = x
  let copyPtr = unsafeAddr(copy[0])
  copyPtr[] = val
  result = copy

proc dontChangeArgWhenCopyIsDeclaredAsVar(x: seq[int], val: int): seq[int] =
  var copy = x
  let copyPtr = unsafeAddr(copy[0])
  copyPtr[] = val
  result = copy

let originalSeq = @[1, 2, 3]
var ret = changeArgDespiteCopyAssignment(originalSeq, 9999)

echo originalSeq
echo ret

ret = dontChangeArgWhenCopyIsDeclaredAsVar(originalSeq, 7777)

echo originalSeq
echo ret

这打印

@[9999, 2, 3]

@[9999, 2, 3]

@[9999, 2, 3]

@[7777, 2, 3]

所以第一个电话改变了originalSeq,而第二个电话没有。有人可以解释引擎盖下发生了什么吗? 我正在使用 Nim 1.6.6 和一个 Nim 新手。

【问题讨论】:

    标签: sequence deep-copy nim-lang copy-assignment


    【解决方案1】:

    我快速浏览了生成的 C 代码,下面是高度编辑和简化的版本。 let code = ... 变体中缺少的基本内容是对genericSeqAssign() 的调用,它会复制参数并将其分配给copy,而不是将参数直接分配给copy。因此,在这种情况下,绝对没有关于分配的深层副本。

    我不知道这是故意的还是代码生成中的错误(我的第一印象是惊讶)。有任何想法吗?

    tySequence_A* changeArgDespiteCopyAssignment(tySequence_A* x, NI val) {
        tySequence_A* result = NIM_NIL;
        tySequence_A* copy;
        NI* copyPtr;
    
        copy = x; /* NO genericSeqAssign() call !*/
    
        copyPtr = &copy->data[(NI) 0];
    
        *copyPtr = val;
    
        genericSeqAssign(&result, copy, &NTIseqLintT_B);
        return result;
    }
    
    tySequence_A* dontChangeArgWhenCopyIsDeclaredAsVar(tySequence_A* x, NI val) {
        tySequence_A* result = NIM_NIL;
        tySequence_A copy;
        NI* copyPtr;
    
        genericSeqAssign(&copy, x, &NTIseqLintT_B);
    
        copyPtr = &copy->data[(NI) 0];
    
        *copyPtr = val;
    
        genericSeqAssign(&result, copy, &NTIseqLintT_B);
        return result;
    }
    

    【讨论】:

      【解决方案2】:

      事实证明,在 nim-lang 问题跟踪器中存在很多与此行为有关的问题。例如:

      let semantics gives 3 different results depends on gc, RT vs VM, backend, type, global vs local scope

      Seq assignment does not perform a deep copy

      Let behaves differently in proc for default gc

      assigning var to local let does not properly copy in default gc

      clarify spec/implementation for let: move or copy?

      RFC give default gc same semantics as --gc:arc as much as possible

      长话短说,是否制作副本取决于很多因素,对于序列,尤其是范围(全局与本地)和使用的 gc(refc、arc、orc)。 更一般地说,所涉及的类型(seq 与数组)、代码生成后端(C 与 JS)以及诸如此类的东西也可能是相关的。

      这种行为欺骗了很多初学者,并没有受到一些贡献者的欢迎。较新的 GC --gc:arc--gc:orc 不会发生这种情况,后者预计将成为即将推出的 Nim 版本中的默认 GC。 由于性能问题、向后兼容性风险以及一旦我们过渡到较新的 GC 它将消失的预期,它从未在当前的默认 gc 中修复。

      就个人而言,我希望它至少在 Nim 语言手册中被明确地单独列出。好吧,它不是。

      【讨论】:

        猜你喜欢
        • 2011-07-08
        • 1970-01-01
        • 2013-04-30
        • 1970-01-01
        • 2011-10-24
        • 2020-09-09
        • 2018-01-14
        • 2015-06-27
        相关资源
        最近更新 更多