feat(gen): ListTest 联合下标修复 + DocTest docstring 完整保留

- CallGenMixin: 默认参数快照无条件查询、静态类调用识别、len() 走 __len__
- TypeGenMixin/AssignGenMixin: T | t.CPtr 泛型标量元素宽度修复
- ExprGenMixin: / → __truediv__、指针判空走 icmp null、__doc__ 完整保留(登记表+跨模块符号解析)
- BuildPipeline: 翻译后提取模块 docstring(预注册阶段跨模块可用)
- Generator: DocKeys/DocValues/DocCount 登记表字段
This commit is contained in:
TermiNexus committed 2026-08-11 21:21:40 +08:00
1 parent 83d46dd646
commit 882c78c67a
6 files changed
+394 -16

No files matched your search

+4
View File
@@ -201,6 +201,10 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy | t.CPtr, file_path: str,
ret: int = tr.translate(tree)
if ret != 0:
return None
# 提取 docstring(预注册阶段所有 App/includes 文件都被翻译,
# 保证跨模块 __doc__ 在正式翻译前已登记;查重幂等)
if gen is not None:
gen.extract_module_docstrings(tree, sha1_val)
return tr
+47 -3
View File
@@ -357,10 +357,27 @@ class AssignGenMixin:
if self._annotation_contains_name(annot, "bytes") != 0:
return 1
# 泛型类型参数/嵌套联合 | t.CPtr(如 _list.vp 的 elem_ptr: T | t.CPtr):
# T 特化后为具体类型(AST|t.CPtr 等指针),联合在解析时可能退化为 i8*,
# 但元素是 8 字节指针,下标必须按 8 字节步长访问——
# 否则 list.get/append 只读写 1 字节,指针被截断 inttoptr 崩溃。
# 先经泛型上下文解析 T 的具体类型——纯标量(int/float 等 sizeof≠8)时
# 元素按标量自身大小(list[int] elem_size=4 匹配 i32),不能按 8 字节
# 指针槽(否则 8 字节写 / 4 字节步长互相覆盖,pop 读到 0);
# 指针/嵌套联合/64 位标量/double(sizeof==8)才按 8 字节。
if self._is_generic_tp_or_nested(bop.left) != 0 or self._is_generic_tp_or_nested(bop.right) != 0:
gp_nm: str = None
if bop.left is not None and bop.left.kind() == ast.ASTKind.Name:
gp_left_nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(bop.left)
gp_nm = gp_left_nm.id
elif bop.right is not None and bop.right.kind() == ast.ASTKind.Name:
gp_right_nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(bop.right)
gp_nm = gp_right_nm.id
if gp_nm is not None:
# 经全局 gen 委托解析泛型参数(T → 具体类型名)。不能 self.GenericTpNames
# 直读(Mixin @t.NoVTable 静态绑定:字段声明在 TypeGenMixin,AssignGenMixin
# 访问会生成不存在的属性引用 → TPC fast-fail 编译崩溃)
gp_resolved: str = HandlesType.resolve_generic_type_name(gp_nm)
if gp_resolved is not None and string.strcmp(gp_resolved, gp_nm) != 0:
# 泛型参数解析为纯标量 → 元素按标量自身大小(非 8 字节指针槽)
if self._is_scalar_ty_name(gp_resolved) != 0:
return 0
return 1
# 64 位标量数值类型 | CPtr(如 CUInt64T | CPtr / CSizeT | CPtr):
# 联合解析后同样退化为指针表示,元素为 8 字节(i64 或指针)。
@@ -393,6 +410,33 @@ class AssignGenMixin:
return 1
return 0
def _is_scalar_ty_name(self, ty_name: str) -> int:
"""判断类型名是否为纯标量(不含指针标记),用于泛型 T 的联合元素类型判定
泛型 T | t.CPtr(如 _list.vp 的 elem_ptr)经 Fix A(TypeGenMixin 联合
解析保留泛型标量)后 alloca 变 Ptr(标量):T 为纯标量(int/float/double/
i64 等)时下标按标量自身大小步长,不能再按 8 字节指针槽——否则
Ptr(标量) alloca + IsPtrElement 路径(load 标量当指针)产生非法 IR。
指针/联合/str/bytes 才保持 8 字节指针槽语义。
"""
if ty_name is None:
return 0
if string.strchr(ty_name, '|') is not None:
return 0
if string.strchr(ty_name, '*') is not None:
return 0
if string.strcmp(ty_name, "str") == 0:
return 0
if string.strcmp(ty_name, "bytes") == 0:
return 0
if string.strcmp(ty_name, "t.CPtr") == 0:
return 0
if string.strcmp(ty_name, "CPtr") == 0:
return 0
if string.strstr(ty_name, "Ptr") is not None:
return 0
return 1
def _is_generic_tp_or_nested(self, node: ast.AST | t.CPtr) -> int:
"""判断注解节点是否为泛型类型参数名(单字母大写,如 T)或嵌套联合(BinOp)"""
if node is None:
+18 -12
View File
@@ -2547,12 +2547,15 @@ class CallGenMixin:
mdf_defaults = mdf_entry.Defaults
mdf_default_count = mdf_entry.DefaultCount
mdf_param_count = mdf_entry.ParamCount
if mdf_defaults is None:
mdf_gd: FuncDefaultsEntry | t.CPtr = HandlesExprCall.find_func_defaults(lookup_name)
if mdf_gd is not None:
mdf_defaults = mdf_gd.Defaults
mdf_default_count = mdf_gd.DefaultCount
mdf_param_count = mdf_gd.ParamCount
# 全局默认参数表查询无条件执行:mdf_entry 命中时(泛型特化方法注册进
# 调用方函数表)Defaults 非 None 会短路跳过,但下方"用快照重建默认值"
# 分支依赖 mdf_gd(唯一持有 stdlib 常量快照处)——缺它必 fatal_error
# (ListTest list[int].__new__ 根因)
mdf_gd: FuncDefaultsEntry | t.CPtr = HandlesExprCall.find_func_defaults(lookup_name)
if mdf_defaults is None and mdf_gd is not None:
mdf_defaults = mdf_gd.Defaults
mdf_default_count = mdf_gd.DefaultCount
mdf_param_count = mdf_gd.ParamCount
# 计算需要填充的参数(不含 self)
# extra_count = 已提供的非 self 参数数量
@@ -3780,12 +3783,15 @@ class CallGenMixin:
mdf_defaults = mdf_entry.Defaults
mdf_default_count = mdf_entry.DefaultCount
mdf_param_count = mdf_entry.ParamCount
if mdf_defaults is None:
mdf_gd: FuncDefaultsEntry | t.CPtr = HandlesExprCall.find_func_defaults(lookup_name)
if mdf_gd is not None:
mdf_defaults = mdf_gd.Defaults
mdf_default_count = mdf_gd.DefaultCount
mdf_param_count = mdf_gd.ParamCount
# 全局默认参数表查询无条件执行:mdf_entry 命中时(泛型特化方法注册进
# 调用方函数表)Defaults 非 None 会短路跳过,但下方"用快照重建默认值"
# 分支依赖 mdf_gd(唯一持有 stdlib 常量快照处)——缺它必 fatal_error
# (ListTest list[int].__new__ 根因)
mdf_gd: FuncDefaultsEntry | t.CPtr = HandlesExprCall.find_func_defaults(lookup_name)
if mdf_defaults is None and mdf_gd is not None:
mdf_defaults = mdf_gd.Defaults
mdf_default_count = mdf_gd.DefaultCount
mdf_param_count = mdf_gd.ParamCount
# 计算需要填充的参数(不含 self)
# arg_count_eff = 已提供的非 self 参数数量(含关键字模式完整构建)
+302
View File
@@ -26,6 +26,19 @@ import lib.Projectrans.Config as Config
import lib.Projectrans.Utils as Utils
# ============================================================
# docstring 登记表容量(__doc__ 魔法属性支持)
#
# DOC_MAX: 登记条目上限(预注册阶段所有 App/includes 文件的
# 函数/类/方法 docstring,幂等查重)
# DOC_KEY_SZ: key "{module_sha1}.{symbol}" 缓冲宽度
# DOC_VAL_SZ: docstring 内容缓冲宽度
# ============================================================
DOC_MAX: t.CDefine = 512
DOC_KEY_SZ: t.CDefine = 192
DOC_VAL_SZ: t.CDefine = 4096
# ============================================================
# ExprGenMixin - 表达式处理 Mixin(对象化 P3,wiki/14-oo-refactor.md)
#
@@ -51,6 +64,11 @@ import lib.Projectrans.Utils as Utils
class ExprGenMixin:
"""表达式处理 Mixin(纯方法化迁移,无状态字段)"""
# --- docstring 登记表字段(__doc__ 魔法属性支持)---
DocKeys: bytes
DocValues: bytes
DocCount: int
# ============================================================
# P3 迁移方法(函数体来自 HandlesExpr.vp 模块级函数,
# 本模块函数调用改 self.,外部模块调用保留模块前缀)
@@ -3169,6 +3187,259 @@ class ExprGenMixin:
return inner_cd
return None
# ============================================================
# docstring 提取/登记/查找(__doc__ 魔法属性支持)
#
# 预注册阶段(BuildPipeline.TranslateFileGetTrans)扫描所有
# App/includes 文件的 AST,提取函数/类/方法的 docstring 内容
# 登记到全局表(key = "{module_sha1}.{symbol}");__doc__ 属性
# 访问时查表,命中则当前模块内联生成字符串常量(无跨模块 IR
# 符号依赖),未命中返回 null。幂等查重,多遍翻译安全。
# ============================================================
def _extract_docstring_from_body(self, body: list[ast.AST | t.CPtr] | t.CPtr) -> str:
"""从函数/类体提取 docstring:body[0] 为 Expr(Constant str) 时返回内容"""
if body is None or body.__len__() == 0:
return None
first: ast.AST | t.CPtr = body.get(0)
if first is None or first.kind() != ast.ASTKind.Expr:
return None
ex: ast.Expr | t.CPtr = (ast.Expr | t.CPtr)(first)
if ex is None or ex.value is None or ex.value.kind() != ast.ASTKind.Constant:
return None
cn: ast.Constant | t.CPtr = (ast.Constant | t.CPtr)(ex.value)
if cn is None or cn.const_kind != ast.CONST_STR:
return None
return cn.str_val
def register_docstring(self, module_sha1: str, symbol: str, doc: str) -> None:
"""登记 docstring(key="{module_sha1}.{symbol}"),查重幂等"""
if module_sha1 is None or symbol is None or doc is None:
return
if string.strlen(doc) == 0:
return
if self.DocKeys is None:
self.DocKeys = stdlib.malloc(DOC_MAX * DOC_KEY_SZ)
self.DocValues = stdlib.malloc(DOC_MAX * DOC_VAL_SZ)
if self.DocKeys is None or self.DocValues is None:
return
string.memset(self.DocKeys, 0, DOC_MAX * DOC_KEY_SZ)
string.memset(self.DocValues, 0, DOC_MAX * DOC_VAL_SZ)
if self.DocCount >= DOC_MAX:
return
ml: t.CSizeT = string.strlen(module_sha1)
sl: t.CSizeT = string.strlen(symbol)
key_buf: bytes = stdlib.malloc(ml + sl + 2)
if key_buf is None:
return
viperlib.snprintf(key_buf, ml + sl + 2, "%s.%s", module_sha1, symbol)
i: int
for i in range(self.DocCount):
if string.strcmp(self.DocKeys + i * DOC_KEY_SZ, key_buf) == 0:
stdlib.free(key_buf)
return
string.strcpy(self.DocKeys + self.DocCount * DOC_KEY_SZ, key_buf)
string.strcpy(self.DocValues + self.DocCount * DOC_VAL_SZ, doc)
self.DocCount += 1
stdlib.free(key_buf)
def lookup_docstring(self, module_sha1: str, symbol: str) -> str:
"""查找 docstring 内容(key="{module_sha1}.{symbol}"),未找到返回 None"""
if self.DocKeys is None or module_sha1 is None or symbol is None:
return None
ml: t.CSizeT = string.strlen(module_sha1)
sl: t.CSizeT = string.strlen(symbol)
key_buf: bytes = stdlib.malloc(ml + sl + 2)
if key_buf is None:
return None
viperlib.snprintf(key_buf, ml + sl + 2, "%s.%s", module_sha1, symbol)
i: int
for i in range(self.DocCount):
if string.strcmp(self.DocKeys + i * DOC_KEY_SZ, key_buf) == 0:
stdlib.free(key_buf)
return self.DocValues + i * DOC_VAL_SZ
stdlib.free(key_buf)
return None
def extract_module_docstrings(self, tree: ast.AST | t.CPtr, module_sha1: str) -> None:
"""扫描模块 AST:登记模块级函数/类/方法的 docstring(幂等)"""
if tree is None or module_sha1 is None:
return
ch: list[ast.AST | t.CPtr] | t.CPtr = tree.children
if ch is None:
return
cn: t.CSizeT = ch.__len__()
for ci in range(cn):
child: ast.AST | t.CPtr = ch.get(ci)
if child is None:
continue
kd: int = child.kind()
if kd == ast.ASTKind.FunctionDef:
fd: ast.FunctionDef | t.CPtr = (ast.FunctionDef | t.CPtr)(child)
if fd is not None and fd.name is not None:
fd_body: list[ast.AST | t.CPtr] | t.CPtr = fd.body
fdoc: str = self._extract_docstring_from_body(fd_body)
if fdoc is not None:
self.register_docstring(module_sha1, fd.name, fdoc)
elif kd == ast.ASTKind.ClassDef:
cd: ast.ClassDef | t.CPtr = (ast.ClassDef | t.CPtr)(child)
if cd is None or cd.name is None:
continue
cd_body0: list[ast.AST | t.CPtr] | t.CPtr = cd.body
cdoc: str = self._extract_docstring_from_body(cd_body0)
if cdoc is not None:
self.register_docstring(module_sha1, cd.name, cdoc)
# 类方法 docstring(key="{ClassName}.{method}")
cd_body: list[ast.AST | t.CPtr] | t.CPtr = cd.body
if cd_body is not None:
cbn: t.CSizeT = cd_body.__len__()
for mi in range(cbn):
m: ast.AST | t.CPtr = cd_body.get(mi)
if m is None or m.kind() != ast.ASTKind.FunctionDef:
continue
mfd: ast.FunctionDef | t.CPtr = (ast.FunctionDef | t.CPtr)(m)
if mfd is None or mfd.name is None:
continue
mdoc: str = self._extract_docstring_from_body(mfd.body)
if mdoc is not None:
cl_nl: t.CSizeT = string.strlen(cd.name)
mt_nl: t.CSizeT = string.strlen(mfd.name)
sym_buf: bytes = stdlib.malloc(cl_nl + mt_nl + 2)
if sym_buf is not None:
viperlib.snprintf(sym_buf, cl_nl + mt_nl + 2, "%s.%s", cd.name, mfd.name)
self.register_docstring(module_sha1, sym_buf, mdoc)
stdlib.free(sym_buf)
def _resolve_doc_symbol_name(self, trans: HT.Translator | t.CPtr, name_id: str,
out_mod: str | t.CPtr, out_sym: str | t.CPtr) -> int:
"""解析 Name 符号的 docstring 归属,写入 out_mod/out_sym,成功返回 1
判定顺序:本地变量(函数变量)→ 结构体类(含跨模块预注册)→ 模块名。
"""
if trans is None or name_id is None or out_mod is None or out_sym is None:
return 0
if trans.ModuleSha1 is None:
return 0
# 1. 本地变量(函数变量/全局变量)
if HandlesVar.lookup_var(trans.SymTab, name_id) is not None:
string.strcpy(out_mod, trans.ModuleSha1)
string.strcpy(out_sym, name_id)
return 1
# 2. 结构体类(entry.ModuleSha1 区分跨模块预注册的类)
se: HandlesStruct.StructEntry | t.CPtr = HandlesStruct.find_struct(name_id)
if se is not None:
if se.ModuleSha1 is not None:
string.strcpy(out_mod, se.ModuleSha1)
else:
string.strcpy(out_mod, trans.ModuleSha1)
string.strcpy(out_sym, name_id)
return 1
# 3. 模块名(模块本身,少见);用 StubMerger 模块级函数(避免
# 跨 Mixin self 调用生成不存在符号)
mod_sha1: str = StubMerger.LookupSha1ByModName(name_id)
if mod_sha1 is None:
mod_sha1 = StubMerger.LookupSha1ByModNameSuffix(name_id)
if mod_sha1 is not None:
string.strcpy(out_mod, mod_sha1)
string.strcpy(out_sym, name_id)
return 1
# 4. 兜底当前模块
string.strcpy(out_mod, trans.ModuleSha1)
string.strcpy(out_sym, name_id)
return 1
def _resolve_doc_symbol_attr(self, trans: HT.Translator | t.CPtr,
at: ast.Attribute | t.CPtr,
out_mod: str | t.CPtr, out_sym: str | t.CPtr) -> int:
"""解析嵌套 Attribute 链的 docstring 归属,写入 out_mod/out_sym
支持形态:remote.remote_func / remote.RemoteClass /
remote.RemoteClass.remote_method / LocalClass.local_method 等。
最左 Name 判定:模块名 → 该模块 sha1;变量/类 → 类所属模块 sha1。
"""
if trans is None or at is None or out_mod is None or out_sym is None:
return 0
if trans.ModuleSha1 is None:
return 0
# 收集 Attribute 链(最外层 attr 在前)
DOC_CHAIN_MAX: t.CInt = 8
items: bytes = stdlib.malloc(DOC_CHAIN_MAX * 128)
if items is None:
return 0
n_items: int = 0
cur: ast.AST | t.CPtr = (ast.AST | t.CPtr)(at)
while cur is not None and cur.kind() == ast.ASTKind.Attribute and n_items < DOC_CHAIN_MAX:
cat: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(cur)
if cat.attr is None:
break
string.strcpy(items + n_items * 128, cat.attr)
n_items += 1
cur = cat.value
# 最左应为 Name
head_id: str = None
if cur is not None and cur.kind() == ast.ASTKind.Name:
hnm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(cur)
head_id = hnm.id
if head_id is None:
stdlib.free(items)
return 0
# head 判定:模块名 or 变量/类
is_mod: int = 0
mod_sha1: str = None
if HandlesVar.lookup_var(trans.SymTab, head_id) is None:
mod_sha1 = StubMerger.LookupSha1ByModName(head_id)
if mod_sha1 is None:
mod_sha1 = StubMerger.LookupSha1ByModNameSuffix(head_id)
if mod_sha1 is not None:
is_mod = 1
if is_mod != 0:
# 模块限定:symbol = items 反转(不含 head_id)
if n_items == 0:
string.strcpy(out_mod, mod_sha1)
string.strcpy(out_sym, head_id)
stdlib.free(items)
return 1
total: t.CSizeT = 0
j: int
for j in range(n_items):
total += string.strlen(items + j * 128) + 1
sym_buf: bytes = stdlib.malloc(total + 1)
if sym_buf is None:
stdlib.free(items)
return 0
sym_buf[0] = '\0'
for j in range(n_items):
if j > 0:
string.strcat(sym_buf, ".")
string.strcat(sym_buf, items + (n_items - 1 - j) * 128)
string.strcpy(out_mod, mod_sha1)
string.strcpy(out_sym, sym_buf)
stdlib.free(sym_buf)
stdlib.free(items)
return 1
# 变量/类:symbol = head_id + '.' + items 反转
total2: t.CSizeT = string.strlen(head_id)
for j in range(n_items):
total2 += string.strlen(items + j * 128) + 1
sym2: bytes = stdlib.malloc(total2 + 1)
if sym2 is None:
stdlib.free(items)
return 0
sym2[0] = '\0'
string.strcat(sym2, head_id)
for j in range(n_items):
string.strcat(sym2, ".")
string.strcat(sym2, items + (n_items - 1 - j) * 128)
se2: HandlesStruct.StructEntry | t.CPtr = HandlesStruct.find_struct(head_id)
if se2 is not None and se2.ModuleSha1 is not None:
string.strcpy(out_mod, se2.ModuleSha1)
else:
string.strcpy(out_mod, trans.ModuleSha1)
string.strcpy(out_sym, sym2)
stdlib.free(sym2)
stdlib.free(items)
return 1
def translate_attribute(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
@@ -3196,6 +3467,37 @@ class ExprGenMixin:
VLogger.error(diag_buf_an, "ATTR")
return None
# 魔法属性 __doc__:查询 docstring 登记表(预注册阶段从 AST 提取),
# 命中则当前模块内联生成字符串常量返回;未命中(无 docstring 的
# 函数/类/方法)返回 null 指针(i8*)。支持本模块与跨模块符号:
# local_func / LocalClass / LocalClass.method / remote.remote_func /
# remote.RemoteClass / remote.RemoteClass.remote_method
if string.strcmp(at.attr, "__doc__") == 0:
doc_mod_buf: bytes = pool.alloc(64)
doc_sym_buf: bytes = pool.alloc(256)
if doc_mod_buf is None or doc_sym_buf is None:
return None
doc_mod_buf[0] = '\0'
doc_sym_buf[0] = '\0'
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
nm_doc: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
if nm_doc.id is not None:
self._resolve_doc_symbol_name(trans, nm_doc.id, doc_mod_buf, doc_sym_buf)
elif at.value is not None and at.value.kind() == ast.ASTKind.Attribute:
self._resolve_doc_symbol_attr(trans, (ast.Attribute | t.CPtr)(at.value), doc_mod_buf, doc_sym_buf)
doc_val: str = None
if doc_mod_buf[0] != '\0' and doc_sym_buf[0] != '\0':
doc_val = self.lookup_docstring(doc_mod_buf, doc_sym_buf)
if doc_val is None:
i8_ty_doc: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
if i8_ty_doc is None:
return None
i8_ptr_ty_doc: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty_doc)
if i8_ptr_ty_doc is None:
return None
return llvmlite.ConstNull(pool, i8_ptr_ty_doc, "null")
return self.create_global_string(builder, pool, mod, doc_val, trans)
# 枚举成员访问: EnumName.MemberName → 常量值
# 当 value 是 Name 且 Name.id 是已注册枚举时,查找成员并返回常量
if at.value.kind() == ast.ASTKind.Name:
+4
View File
@@ -130,6 +130,10 @@ class Generator(BaseGenMixin.BaseGenMixin, TypeGenMixin.TypeGenMixin,
self.EnumCount = 0
# ---- AssignGenMixin 字段 ----
self.RecoverFound = 0
# ---- ExprGenMixin docstring 字段(__doc__ 魔法属性支持)----
self.DocKeys = None
self.DocValues = None
self.DocCount = 0
# ============================================================
+19 -1
View File
@@ -1757,7 +1757,25 @@ class TypeGenMixin:
other_is_name = 1
if other_ty is not None and other_is_name != 0:
if HandlesStruct.find_struct_by_type(other_ty) is None:
other_ty = None
# 泛型类型参数(T→int 等)经上下文替换解析出的合法标量
# 必须保留——"Name 且未注册 struct 即丢弃"本为拦截损坏
# 节点解析出基础类型垃圾,但会误杀泛型替换的合法标量,
# 导致 T | t.CPtr(T=int)退化为 i8*:list[int] elem_ptr
# 按 8 字节槽写、elem_size=4 步长读 → 元素互相覆盖,
# pop 读到 0(ListTest 根因)。泛型参数名放行。
keep_other_ty: int = 0
if self.GenericTpNames is not None:
other_nm_x: ast.Name | t.CPtr = (ast.Name | t.CPtr)(other_node)
gtn_nx: t.CSizeT = self.GenericTpNames.__len__()
gtn_ix: t.CSizeT
for gtn_ix in range(gtn_nx):
gtn_nmx: str = self.GenericTpNames.get(gtn_ix)
if gtn_nmx is not None and other_nm_x.id is not None:
if string.strcmp(gtn_nmx, other_nm_x.id) == 0:
keep_other_ty = 1
break
if keep_other_ty == 0:
other_ty = None
if other_ty is None:
# 严格模式(App 文件)下裸类名不可见时,回退到全局
# struct 注册表按类名查找。X | t.CPtr 是明确的指针包装