You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python与ctypes:传引用传递结构体时遇空指针访问错误

问题:调用返回结构体数组的DLL时出现“Null Pointer Access”错误

我写了一个会分配内存并填充结构体后返回给Python的DLL,但在Python调用时触发了“Null Pointer Access”错误。

C代码(getatt.c,编译为getatt.dll)

typedef struct _ATTO {
    char title[132 + 1];
    char val[132 + 1];
} ATT;

__declspec(dllexport) void get_file_attributes(char* psFileName, ATT** *StructAtt, int* iTotalAtt) {
    int j = 0;
    int iError = NONE;
    char cMessage[132 + 1] = "";
    int iNumAtt = 5;

    StructAtt = (ATT***)malloc(iNumAtt * sizeof(ATT**));
    if(StructAtt == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return;
    }
    if(iNumAtt > 0) {
        for(i = 0; i < iNumAtt; i++) {
            StructAtt[i] = malloc(sizeof(ATT*));
            if(StructAtt[i] == NULL) {
                fprintf(stderr, "Memory allocation failed\n");
                return ;
            }

            for(j = 0; j < 1; j++) {
                StructAtt[i][j] = (ATT*)malloc(sizeof(ATT));
                if(StructAtt[i][j] == NULL) {
                    fprintf(stderr, "Memory allocation failed\n");
                    return ;
                }

                strcpy((StructAtt[i][j])->title, psStructAttInfoTitle[i]); //psStructAttInfoTitle是标题数组
                strcpy((StructAtt[i][j])->val, psStructAttInfoVal[i]);   //psStructAttInfoVal是值数组
                sprintf(cMessage, "%s=%s\n", StructAtt[i][j]->title, StructAtt[i][j]->val);
                //  write_output(cMessage);
            }
        }
    }

    // 打印值
    for(i = 0; i < iNumAtt; i++) {
        for(j = 0; j < 1; j++) {
            sprintf(message, "%s=%s\n", (StructAtt)[i][j]->title, (StructAtt)[i][j]->val);
            write_output(message);
        }
    }

    *iTotalAtt = iNumAtt;
    return;
}

DLL运行输出

TYPE=0
SUF=C
GROUP=a
OWNER=unknown
NUMSUF=467

Python调用代码

import ctypes
from ctypes import *

class PY_STRUCT(Structure):
    _fields_ = [('title', c_char * 133),
                ('value', c_char * 133)]

file_name = 'Forest23wd'
file_att_dll = CDLL('./getatt')
file_att_dll.get_file_attributes.argtypes = [c_char_p, POINTER(POINTER(POINTER(PY_STRUCT))),POINTER(c_int)]

file_name_str = c_char_p(file_name.encode('utf-8'))
attr_list = POINTER(POINTER(PY_STRUCT))()
total_attr = c_int(0)

file_att_dll.get_file_attributes(file_name_str, byref(attr_list), byref(total_attr))

# 访问数据
print(total_attr.value)
print(attr_list[0][0].contents.title) # 触发错误:ValueError: NULL pointer access...

我能获取到total_attr.value为5,但访问attr_list[0][0]时触发空指针错误,预期这里应该返回“TYPE”。


错误原因分析

  1. 指针传递错误:C函数中ATT*** StructAtt是按值传递的,你直接给StructAtt分配内存,修改的只是函数内部的局部变量,并没有把分配好的地址传递回Python,导致Python侧的attr_list始终是空指针。
  2. 冗余的指针层级:这里完全不需要三级指针(ATT***),用二级指针(ATT**)就足够返回结构体数组,三级指针徒增复杂度。
  3. 字段名不匹配:Python结构体中定义的是value,但C结构体中是val,会导致内存解析错误。

修正方案

1. 修正C代码

调整参数传递方式,通过二级指针返回结构体数组,同时简化内存分配逻辑,添加内存泄漏防护:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 测试用的标题和值数组
const char* psStructAttInfoTitle[] = {"TYPE", "SUF", "GROUP", "OWNER", "NUMSUF"};
const char* psStructAttInfoVal[] = {"0", "C", "a", "unknown", "467"};
#define NONE 0

typedef struct _ATTO {
    char title[133];
    char val[133];
} ATT;

__declspec(dllexport) void get_file_attributes(char* psFileName, ATT** *pStructAtt, int* iTotalAtt) {
    int i;
    int iNumAtt = 5;

    // 给结构体数组指针分配内存,通过*pStructAtt传递回调用者
    *pStructAtt = (ATT**)malloc(iNumAtt * sizeof(ATT*));
    if(*pStructAtt == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        *iTotalAtt = 0;
        return;
    }

    for(i = 0; i < iNumAtt; i++) {
        // 给单个结构体分配内存
        (*pStructAtt)[i] = (ATT*)malloc(sizeof(ATT));
        if((*pStructAtt)[i] == NULL) {
            fprintf(stderr, "Memory allocation failed\n");
            // 释放已分配的内存,避免泄漏
            for(int k = 0; k < i; k++) {
                free((*pStructAtt)[k]);
            }
            free(*pStructAtt);
            *pStructAtt = NULL;
            *iTotalAtt = 0;
            return ;
        }

        // 安全填充字符串,避免缓冲区溢出
        strncpy((*pStructAtt)[i]->title, psStructAttInfoTitle[i], 132);
        (*pStructAtt)[i]->title[132] = '\0';
        strncpy((*pStructAtt)[i]->val, psStructAttInfoVal[i], 132);
        (*pStructAtt)[i]->val[132] = '\0';

        printf("%s=%s\n", (*pStructAtt)[i]->title, (*pStructAtt)[i]->val);
    }

    *iTotalAtt = iNumAtt;
    return;
}

// 导出内存释放函数,避免Python侧内存泄漏
__declspec(dllexport) void free_attributes(ATT** attrs, int count) {
    for(int i = 0; i < count; i++) {
        free(attrs[i]);
    }
    free(attrs);
}

2. 修正Python代码

调整结构体字段名、指针类型,并添加内存释放逻辑:

import ctypes
from ctypes import *

class PY_STRUCT(Structure):
    _fields_ = [('title', c_char * 133),
                ('val', c_char * 133)]  # 和C结构体字段名保持一致

file_name = 'Forest23wd'
file_att_dll = CDLL('./getatt')

# 调整参数类型,对应C的ATT**
file_att_dll.get_file_attributes.argtypes = [c_char_p, POINTER(POINTER(PY_STRUCT)), POINTER(c_int)]
file_att_dll.get_file_attributes.restype = None

# 注册内存释放函数
file_att_dll.free_attributes.argtypes = [POINTER(PY_STRUCT), c_int]
file_att_dll.free_attributes.restype = None

file_name_str = file_name.encode('utf-8')
attr_list = POINTER(PY_STRUCT)()  # 对应C的ATT**
total_attr = c_int(0)

file_att_dll.get_file_attributes(file_name_str, byref(attr_list), byref(total_attr))

# 访问并打印数据
print(f"总属性数: {total_attr.value}")
for i in range(total_attr.value):
    title = attr_list[i].title.decode('utf-8')
    val = attr_list[i].val.decode('utf-8')
    print(f"{title} = {val}")

# 释放内存,避免泄漏
file_att_dll.free_attributes(attr_list, total_attr)

内容的提问来源于stack exchange,提问作者nas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 07:54:54