2

In Ubuntu 14.04, I wrote a C file called hash.c:

/* hash.c: hash table with linear probing */

typedef struct {
    void *key;
    void *value;
} ht_entry;

typedef struct {
    ht_entry *table;
    int len;
    int num_entries;
    int (*hash_fn)(void *key);
    int (*key_cmp)(void *k1, void *k2);
} hashtable;

and compiled it with

gcc -shared hash.c -o test.so -fPIC

Afterwards, I tried to load test.so in a Python script (for testing), but I got the following error: "OSError: .../test.so: undefined symbol: hash_fn"

hash_fn is a function pointer in the hashtable struct. It is referenced a number of times by functions later in the file.

I do not understand why this error is happening. I have Googled but all other cases either concern C++ or includes. In my case I just have 1 C file that includes only stdio and stdlib.


here is the FULL code. When I comment out all but hash_create and print_info, it loads succesfully. When I uncomment find(), it the error happens. (print_info is just for testing that ctypes works)

/* hash.c: hash table with linear probing */

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

typedef struct {
    void *key;
    void *value;
} ht_entry;

typedef struct {
    ht_entry *table;
    int len;
    int num_entries;
    int (*hash_fn)(void *key);
    int (*key_cmp)(void *k1, void *k2);
} hashtable;

static void close_gap(hashtable *ht, int i);
static int find(hashtable *ht, void *key);

hashtable* hash_create(int len, int (*hash_fn)(void*), int (*key_cmp)(void*, void*))
{
    hashtable* ht = (hashtable*) malloc(sizeof(hashtable));
    ht->len = len;
    ht->table = calloc(len, sizeof(ht_entry));  
    ht->hash_fn = hash_fn;
    ht->key_cmp = key_cmp;
    ht->table[0].key = 2;
    ht->table[0].value = 3;
    return ht;
}

void print_info(hashtable *ht)
{
    printf("%d, %d, %d\n", ht->len, ht->table[0].key, ht->table[0].value);
}

void* hash_retrieve(hashtable* ht, void *key)
{
    int i = find(ht, key);
    if(i < 0) {
        return NULL;
    }

    return ht->table[i].value;
}

void hash_insert(hashtable* ht, void *key, void *value)
{
    if(ht->num_entries == ht->len) {
        return;
    }

    int i = hash_fn(key) % ht->len;
    while(ht->table[i].key != NULL) {
        i = (i + i) % ht->len;
    }
    ht->table[i].key = key;
    ht->table[i].value = value;
}

void hash_remove(hashtable *ht, void *key)
{
    int i = find(ht, key);
    if(i < 0) {
        return;
    }   
    ht->table[i].key = 0;
    ht->table[i].value = 0;
    close_gap(ht, i);
}

static int find(hashtable *ht, void *key)
{
    int i = hash_fn(key) % ht->len;
    int num_checked = 0;
    while(ht->table[i].key && num_checked != ht->len) {
        if(!ht->key_cmp(ht->table[i].key, key)) {
            return i;
        }
        num_checked++;
        i = (i + i) % ht->len;
    }
    return -1;
}

static void close_gap(hashtable *ht, int i)
{
    int j = (i + 1) % ht->len;
    while(ht->table[j].key) {
        int loc = ht->hash_fn(ht->table[j].key);
        if((j > i && (loc <= i || loc > j)) || (j < i && (loc <= i && loc > j))) {
            ht->table[i] = ht->table[j];
            ht->table[j].key = 0;
            ht->table[j].value = 0;
            close_gap(ht, j);
            return;
        }
    }
}
1
  • it looks like hash_fn is expected to be a global function. Struct member names are not compiled as symbols into the executable. Commented Jul 6, 2015 at 7:26

1 Answer 1

4

When I use your compilation line I get five warnings. There are several problems here. First you are trying to assign an int to void * in several places. That raises a warning, and it would crash at runtime because you are passing 2 and 3 as addresses.

Second, you are calling hash_fn in a couple of places instead of ht->hash_fn. That causes the linker error, but you should consider my other changes, otherwise it will crash at runtime with a SIGSEGV:

/* hash.c: hash table with linear probing */

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

typedef struct {
    void *key;
    void *value;
} ht_entry;

typedef struct {
    ht_entry *table;
    int len;
    int num_entries;
    int (*hash_fn)(void *key);
    int (*key_cmp)(void *k1, void *k2);
} hashtable;

static void close_gap(hashtable *ht, int i);
static int find(hashtable *ht, void *key);


hashtable* hash_create(int len, int (*hash_fn)(void*), int (*key_cmp)(void*, void*))
{
    hashtable* ht = (hashtable*) malloc(sizeof(hashtable));
    ht->len = len;
    ht->table = calloc(len, sizeof(ht_entry));
    ht->hash_fn = hash_fn;
    ht->key_cmp = key_cmp;

    // <<< Code changed here
    /*
    ht->table[0].key = 2;
    ht->table[0].value = 3;
    */

    {
        int *p = malloc(sizeof(int));
        *p = 2;
        ht->table[0].key = p;

        p = malloc(sizeof(int));
        *p = 3;
        ht->table[0].value = p;
    }
    // end of code change   

    return ht;
}

void print_info(hashtable *ht)
{
    // <<<<  Code changed
    printf("%d, %d, %d\n", ht->len,
           *(int *)ht->table[0].key, *(int *)ht->table[0].value);
}

void* hash_retrieve(hashtable* ht, void *key)
{
    int i = find(ht, key);
    if(i < 0) {
        return NULL;
    }

    return ht->table[i].value;
}

void hash_insert(hashtable* ht, void *key, void *value)
{
    if(ht->num_entries == ht->len) {
        return;
    }

    // <<<  Code changed
    int i = ht->hash_fn(key) % ht->len;

    while(ht->table[i].key != NULL) {
        i = (i + i) % ht->len;
    }
    ht->table[i].key = key;
    ht->table[i].value = value;
}

void hash_remove(hashtable *ht, void *key)
{
    int i = find(ht, key);
    if(i < 0) {
        return;
   ht->table[i].key = 0;
    ht->table[i].value = 0;
    close_gap(ht, i);
}

static int find(hashtable *ht, void *key)
{
    // <<<  Code changed
    int i = ht->hash_fn(key) % ht->len;

    int num_checked = 0;
    while(ht->table[i].key && num_checked != ht->len) {
        if(!ht->key_cmp(ht->table[i].key, key)) {
            return i;
        }
        num_checked++;
        i = (i + i) % ht->len;
    }
    return -1;
}


static void close_gap(hashtable *ht, int i)
{
    int j = (i + 1) % ht->len;
    while(ht->table[j].key) {
        int loc = ht->hash_fn(ht->table[j].key);
        if((j > i && (loc <= i || loc > j)) || (j < i && (loc <= i && loc > j))) {
            ht->table[i] = ht->table[j];
            ht->table[j].key = 0;
            ht->table[j].value = 0;
            close_gap(ht, j);
            return;
        }
    }
}

I only coded around the errors and warnings, I did not check the logic. You will see that I have used malloc to allocate memory for key and value. Obviously you will need memory management on these two (i.e. free()).

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you very much for the fix. It did not occur to me that -shared compilation does not resolve all symbols, which is why gcc didn't cause an error when I referred to hash_fn instead of ht->hash_fn. So I did not notice it until you pointed it out
@user1299784: it gave a linker error on the gcc I am using on OS X.
Double checked, no error on Ubuntu 14.04. I used "gcc -shared hash.c -o test.so -fPIC" -- are these the correct settings for creating shared libs for ctypes?
@user1299784: yes, I used exactly the same, I copied and pasted your command line. Its just probably different versions of gcc. By the way, I also add -Wall to show all warnings.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.