My 2D array does not work properly in Ctypes

Viewed 87

main.c:

#include <stdlib.h>

int **function() {
    int **information = malloc(5 * sizeof(int));
    for (int k = 0; k < 5; k++) {
        information[k] = malloc(5 * sizeof(int));
        for (int j = 0; j < 5; j++) {
            information[k][j] = j;
        }
    }
    return information;
}

main.py

import ctypes
from numpy.ctypeslib import ndpointer

lib = ctypes.CDLL("C:\\Users\\.....\\Desktop\\test9\\a.dll")

lib.function.restype = ndpointer(dtype=ctypes.c_int, shape=(5,5),flags='C')

res = lib.function()
print(res)

Result:

[[222866112       368 222866144       368 222866176]
 [      368 222866208       368 222866672       368]
 [        2         3         4         0 389116888]
 [201333630         0         1         2         3]
 [        4         0 389116888 201333630         0]]

I do not know why it gives such an output and I have no problem with the same method in one-dimensional mode

How can I solve this problem?

Edited: Both methods can be useful

2 Answers

Your code allocates an array of 5 pointers to arrays of 5 int. The allocation for the pointer array is incorrect: instead of int **information = malloc(5 * sizeof(int)); you should write: int **information = malloc(5 * sizeof(*information));

Yet for your purpose, the type int ** is incorrect. You should instead allocate an actual 2D array int array[5][5].

The syntax for a pointer to a dynamically allocated 2D array is cumbersome and the syntax for a function that returns such a pointer is a real challenge:

#include <stdlib.h>

int (*function(void))[5] {
    // allocate an array of 5 arrays of 5 int
    int (*information)[5] = malloc(5 * sizeof(*information));
    // initialize all 5 rows to identical vectors { 0, 1, 2, 3, 4 }
    for (int k = 0; k < 5; k++) {
        for (int j = 0; j < 5; j++) {
            information[k][j] = j;
        }
    }
    return information;
}

The function prototype can be read using the spiral rule: start from the name, read the postfix operators then the prefix operators, switching direction at the parentheses:

function is a function of no arguments returning a pointer to arrays of 5 int.

To use ctypes with int** you can use POINTER(POINTER(c_int)) as the type and use slicing to access the elements of the array. To use ctypes with a C-contiguous 5x5 array using int* then numpy can be used efficiently.

test.c

#include <stdlib.h>

#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

API int **function() {
    int **information = malloc(5 * sizeof(int*)); // Note row element is int*
    for (int k = 0; k < 5; k++) {
        information[k] = malloc(5 * sizeof(int)); // Note col element is int
        for (int j = 0; j < 5; j++) {
            information[k][j] = k * 5 + j;
        }
    }
    return information;
}

API int *function2() {
    int *information = malloc(5 * 5 * sizeof(int)); // 5 x 5 C contiguous shape
    for (int k = 0; k < 5; k++) {
        for (int j = 0; j < 5; j++) {
            information[k * 5 + j] = k * 5 + j;
        }
    }
    return information;
}

test.py

import ctypes as ct
import numpy as np
from pprint import pprint

lib = ct.CDLL('./test')
lib.function.argtypes = ()
lib.function.restype = ct.POINTER(ct.POINTER(ct.c_int))
lib.function2.argtypes = ()
lib.function2.restype = np.ctypeslib.ndpointer(dtype=ct.c_int, shape=(5, 5), flags='C')

def function():
    res = lib.function()
    # slicing used to extract the correct number of items
    return [row[:5] for row in res[:5]]

pprint(function())
pprint(lib.function2())

Output:

[[0, 1, 2, 3, 4],
 [5, 6, 7, 8, 9],
 [10, 11, 12, 13, 14],
 [15, 16, 17, 18, 19],
 [20, 21, 22, 23, 24]]
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24]])

In both cases the memory is leaked for the mallocs. Ideally, let Python manage the memory and let C know the shape. Using np.empty below allocates memory without initialization then C initializes it. This is more flexible and can be used with different-sized arrays:

test.c

#include <stdlib.h>

#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

API void function3(int* information, int row, int col) {
    for (int k = 0; k < row; k++) {
        for (int j = 0; j < col; j++) {
            information[k * row + j] = k * 5 + j;
        }
    }
}

test.py

import ctypes as ct
import numpy as np

lib = ct.CDLL('./test')
lib.function3.argtypes = np.ctypeslib.ndpointer(dtype=ct.c_int),
lib.function3.restype = None

info = np.empty(dtype=ct.c_int,shape=(5,5))
lib.function3(info,5,5)
print(info)

Output:

[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]]
Related