Golang中MulUintptr实现原理

2,364 阅读1分钟

前言

  • 最近在看channel源码时,看到一个函数MulUintptr,功能很简单,就是把两个数相乘,看是否越界,在golang很多地方都有使用,用于判断内存申请
  • 如果越界就返回false,否则返回true ,代码如下

go/sys/MulUintptr源码链接

  
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package math

import "runtime/internal/sys"

const MaxUintptr = ^uintptr(0)

// MulUintptr returns a * b and whether the multiplication overflowed.
// On supported platforms this is an intrinsic lowered by the compiler.
func MulUintptr(a, b uintptr) (uintptr, bool) {
	if a|b < 1<<(4*sys.PtrSize) || a == 0 {
		return a * b, false
	}
	overflow := b > MaxUintptr/a
	return a * b, overflow
}

说明

  • 没搞明白的点在于a|b < 1<<(4*sys.PtrSize) || a== 0,看了半天没看懂
  • 在网上查了一下,只发现了用法,也没找到为什么这么写
  • 最后晚上下班在车上才终于想明白

分析

  • 首先a==0,没什么可说的,如果a=0,那么肯定不会越界

  • 重点落在了**a|b < 1<<(4*sys.PtrSize) **

  • sys.PtrSize在64位机器中为8,所以上面的代码等价于a|b < 1<<32

  • 我们知道64位计算中最大的无符号数是2^64-1

  • 看到2^32和2^64,再联想到函数的作用是乘法,一下子就明白了

  • 有两个条件要达成共识

    • 相乘的两个数很少会特别大,很少会大于等于2^32
    • 当a|b<2^32时,则a<2^32, b<2^32
      • 位运算,这个应该大家都了解
  • 由此得出,如果要判断两个相乘是否大于等于2^64,只要这两个数都小于2^32就可以了

  • 这种情况可覆盖99.9%的情况

  • 剩下的情况用verflow := b > MaxUintptr/a就能兜底了

翻译一下代码如下

func MulUintptr(a, b uintptr) (uintptr, bool) {
	if ab都小于2^32 或者 a 等于 0 时{
		return a * b, 越界
	}
    // a*b如果越界了,得到的数一定比2^64-1overflow := b > MaxUintptr/a
	return a * b, overflow
}