cvoid-pointersgdbm

GDBM object passed via void pointer is lost/corrupted


I have a program that can use either GDBM or Kyoto Cabinet as a DBM library. I've written some functions to abstract away the difference between the two and I pass around void pointers in place of the database file (GDBM_FILE in the case of GDBM and KCDB * in the case of Kyoto Cabinet). Everything with KC works fine, however when I try using the GDBM backend, the database somehow gets "lost" in passing it around to different functions. When I try to cast the pointer and dereference it, and then pass that to one of the GDBM functions, it segfaults and in a debugger it complains about the db file not existing.

Here's some code that can reproduce the problem:

#include <gdbm.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>

void *
dbopen (void)
{
  printf ("opening\n");
  GDBM_FILE database = gdbm_open ("test.db", 512, GDBM_WRCREAT,
                                  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, NULL);
  if (!database)
    {
      printf ("cannot open database\n");
      return NULL;
    }
  void *db = &database;
  return db;
}

void
dbclose (void *db, void *foo)
{
  printf ("%d\n", *(int *)foo);
  GDBM_FILE database = *(GDBM_FILE *)db;
  if (!database)
    {
      printf ("database lost\n");
      return;
    }
  printf ("closing\n");
  gdbm_close (database);
  return;
}

void
fun (void *db, void *foo)
{
  GDBM_FILE database = *(GDBM_FILE *)db;
  datum key, value;
  int bar = *(int *)foo;  /* and yet, if I remove this line and the next */
  printf ("%d\n", bar);   /* one, it works! */
  printf ("%d\n", *(int *)foo);
  if (!database)
    printf ("no db?\n");
  key.dptr = "baz";
  key.dsize = 4;
  value.dptr = "quux";
  value.dsize = 4;
  printf ("storing\n");
  gdbm_store (database, key, value, GDBM_REPLACE);
  printf ("all done\n");
  return;
}

int
main (void)
{
  int foo = 5;
  void *dbp = dbopen ();
  void *foop = &foo;
  fun (dbp, foop);
  dbclose (dbp, foop);
}

When I run that code, it segfaults with the "file not found" error on the call to gdbm_close(). As the comments indicate, if I don't explicitly store the other void pointer to an int, then the program runs just fine.

In my actual program, it gets "lost" when I call gdbm_store(), and it's the only void pointer I'm using (in this test program, the foo pointer was just supposed to be a sanity check).

I'm sure there is something in the vagaries of memory allocation in C that I'm forgetting or not understanding. Why does the void pointer referencing to the GDBM database get lost/corrupted when the void pointer referencing the int does not? Why, when I don't try to store the dereferenced void pointer foo to an int, does it suddenly work?


Solution

  • Your issue is in getting the address of the pointer returned from gdbm_open instead if its value. The confusing part comes from the definition of GDBM_FILE - its a pointer to a struct:

    typedef struct {int dummy[10];} *GDBM_FILE;
    

    You get its address, that is the location of the pointer, not the pointer value:

    void *db = &database;
    

    The problem can be solved by replacing the conversion lines with:

    void *db = (void *)database;
    

    and

    GDBM_FILE database = (GDBM_FILE)db;