【问题标题】:Does 'array[array.length - 1] = array.pop()' yield undefined behavior?'array[array.length - 1] = array.pop()' 会产生未定义的行为吗?
【发布时间】:2019-11-27 10:49:57
【问题描述】:

我一直在尝试实现一种方法,该方法将数组和索引作为输入,将给定数组的最后一个元素复制到给定索引处的条目中,然后截断最后一个元素(即设置数组短一)。

以下是我为这项任务提供的建议功能:

function swapWithLastAndRemove(array, index) {
    array[index] = array.pop();
}

然后我测试了这个函数以确保它适用于任何给定的索引:

for (let index = 0; index < 5; index++) {
    var array = [0, 1, 2, 3, 4];
    swapWithLastAndRemove(array, index);
    console.log(array);
}

我发现它对除最后一个索引之外的所有索引都正常工作:

[ 4, 1, 2, 3 ]
[ 0, 4, 2, 3 ]
[ 0, 1, 4, 3 ]
[ 0, 1, 2, 4 ]
[ 0, 1, 2, 3, 4 ]

换句话说,对于最后一个元素,它会将其从数组中弹出,然后将其重写到数组中。

让我印象深刻的是,这一行:

array[index] = array.pop();

在最后一个元素上执行时相当于这一行:

array[array.length - 1] = array.pop();

不可能导致array 的内容等于[ 0, 1, 2, 3, 4 ]。

在我看来,这里有两种选择:

  1. 语句 array.pop() 在计算表达式 array.length - 1 之前执行
  2. 语句 array.pop() 在表达式 array.length - 1 被计算后执行

在第一种情况下,数组的内容会发生如下变化:

  • [ 0, 1, 2, 3, 4 ] // initial state
  • [ 0, 1, 2, 3 ] // after popping 4
  • [ 0, 1, 2, 4 ] // after assignment

在第二种情况下,它应该触发了某种内存访问冲突,因为当数组的长度仅为 4 时,将尝试写入数组中的第 5 个条目 (array[4])条目。

我知道 NodeJS 可能正在应用一些内存管理方案,它会以某种方式允许它在没有“数组索引超出范围”异常的情况下完成,但我仍然不明白为什么它会让这个操作“逃脱它”,以及为什么结果是这样的。

array[array.length - 1] = array.pop() 行可能是未定义的行为吗?

JavaScript 中是否存在未定义的行为?

【问题讨论】:

  • 取最后一个索引想要什么结果?
  • "在第二种情况下,它应该触发了某种内存访问冲突," 为什么? arr = []; arr[42] = "hello" 不是问题。 JS 中的数组是动态的,并不真正对应于大多数其他语言中的数组,而是更像 Java 中的 ArrayList
  • 不,在 JS wrt 评估顺序中没有未定义的行为 - 它总是从左到右。 arrray.length-1 表达式在 array.pop() 调用之前评估

标签: javascript arrays node.js


【解决方案1】:

第二个选项:语句array.pop()在表达式之后执行 array.length - 1 被评估

确实是这样。 JS 评估总是从左到右。它评估 array.length - 1 以索引数组的 4,然后从数组中弹出最后一个元素,然后将该元素分配给索引 4。

在第二种情况下,它应该触发了某种内存访问冲突,因为当数组长度仅为 4 时,将尝试写入数组中的第 5 个条目 (array[4])条目。

不,JS 中没有内存访问冲突。它只是再次创建一个新属性,并相应地更改数组的长度。这与代码发生的情况相同

const array = [0, 1, 2, 3, 4];
const element = array.pop();
console.log(JSON.stringify(array)); // [0,1,2,3]
array[4] = element;
console.log(JSON.stringify(array)); // [0,1,2,3,4]

类似地,填充数组使用相同的功能:

const array = [];
for (let i=0; i<5; i++)
    array[i] = i; // no memory access violation
console.log(array.length); // 5

【讨论】:

    【解决方案2】:

    最后一个索引结果是[0, 1, 2, 3, &lt;empty&gt;, 4],而不是[ 0, 1, 2, 3, 4 ]:

    function swapWithLastAndRemove(array, index) {
      array[index] = array.pop();
    }
    
    var array = [0, 1, 2, 3, 4];
    swapWithLastAndRemove(array, 5);
    console.log(array);

    这里不用担心执行顺序,因为index 是恒定的 - 参数index 永远不会重新分配,所以传递给swapWithLastAndRemove 的数字将始终是index,即弹出的项目分配给。考虑到它引入了不必要的混乱 - 更容易记住传递的 index 永远不会重新分配。

    发生了什么:

    (1)数组的最后一项被弹出(4),导致数组变为[0, 1, 2, 3]

    (2) 传入的index 是5,所以array[5] 设置为4。该操作相当于:

    const arr = [0, 1, 2, 3];
    arr[5] = 4;
    console.log(arr);

    这会导致 稀疏数组 - 索引 4 处不再有项目。这不是未定义的行为,只是很奇怪,因为稀疏数组很奇怪。

    Javascript 中是否存在未定义的行为?

    是的,请参阅here。

    【讨论】:

    • 我认为@goodvibration 正试图在arr[4], not arr[5] 分配值
    • 抱歉,您对swapWithLastAndRemove(array, 5) 的调用不是我在代码中所做的,也不是我想要做的。 Javascript(以及据我所知的任何其他语言)中的索引都是从 0 开始的。所以在我的例子中,最后一个元素的索引是 4,而不是 5。这也是我在上面的编码示例中使用的索引。据我所知,这会使您的整个答案变得无关紧要(尽管部分答案可能是正确的,但我不能承认我在看到“5”后就停止阅读了)。
    • @HarunurRashid:是的,你的评论是正确的。
    猜你喜欢
    • 2019-11-16
    • 1970-01-01
    • 2015-11-16
    • 2018-10-15
    • 2011-08-25
    • 2013-04-24
    • 2019-08-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多