Question

Seeing some unusual behaviour when naming an object with the same name as its typedef. When I define _same_names_, the declaration in clist_create will expand to

clist *clist;

and somehow this seems to be causing a segfault. If I change this declaration to another name (clist_base), issue disappears and program appears to execute normally. I've reduced it to the following:

/*
 * naming-test.c
 * Strange behaviour when using the same name for an object as its typedef eg.
 *   clist *clist; //where clist is a typedef
 */

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

typedef void *(*allocator_t)(size_t size);
typedef void (*deallocator_t)(void *obj);

typedef struct clist
{
  void *data;
  struct clist *next;
  struct clist *prev;
  allocator_t allocate;
  deallocator_t deallocate;
}clist;

/* simple wrapper so we can log the allocd memory */
void *log_alloc(size_t size)
{
  void *ptr = malloc(size);
  fprintf(stdout, "+%p\n", ptr);
  return ptr;
}

/* logs the freed memory */
void log_free(void *ptr)
{
  fprintf(stdout, "-%p\n", ptr);
  free(ptr);
}

#ifdef _same_name_
#define my_clist_object_name clist
#else /* different names */
#define my_clist_object_name clist_base
#endif
clist *clist_create(int objsize, int units_in_block, allocator_t allocator, deallocator_t deallocator)
{
  allocator_t default_allocator = malloc;
  deallocator_t default_deallocator = free;
  clist *my_clist_object_name;

  if(!allocator || !deallocator)
  {
    allocator = default_allocator;
    deallocator = default_deallocator;
  }
  my_clist_object_name = allocator(  /* line 53 */
      sizeof(clist));
  if(!my_clist_object_name)
  {
    goto err0;
  }
  my_clist_object_name->data = 0; 
  my_clist_object_name->next = 0; /* line 60 */
  my_clist_object_name->prev = 0;
  my_clist_object_name->allocate = allocator; /* line 62 */
  my_clist_object_name->deallocate = deallocator;
  return my_clist_object_name;

err0:
  return 0;
}

void clist_delete(clist *clist_head)
{
  clist *cursor, *next;
  cursor = clist_head;
  while(cursor)
  {
    next = cursor->next; 
    cursor->deallocate(cursor); /* line 77 */
    cursor = next;
  }
}
void test_clist_create()
{
  clist *clist_renamed = clist_create(sizeof(int), 2, &log_alloc, &log_free);
  if(!clist_renamed)return ;
  clist_delete(clist_renamed);
}

int main()
{
  test_clist_create();
  fprintf(stdout, "not crashing anymore!!\n");
  return 0;
}

I'm using this Makefile (no complaints from gcc)

#Makefile for naming-test.c
CFLAGS=-g -Wall -std=c89

same_names:
    gcc ${CFLAGS} -D_same_name_ naming-test.c -o same_names
different_names:
    gcc ${CFLAGS} naming-test.c -o different_names
clean:
    rm -f same_names different_names

The interesting part: same_names is segfaulting, and gdb gives the following backtrace

(gdb) bt
#0  0x00007ffff7a85475 in *__GI_raise (sig=<optimized out>) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1  0x00007ffff7a886f0 in *__GI_abort () at abort.c:92
#2  0x00007ffff7ac052b in __libc_message (do_abort=<optimized out>, fmt=<optimized out>) at ../sysdeps/unix/sysv/linux/libc_fatal.c:189
#3  0x00007ffff7ac9d76 in malloc_printerr (action=3, str=0x7ffff7ba2190 "free(): invalid next size (fast)", ptr=<optimized out>)
    at malloc.c:6283
#4  0x00007ffff7aceaac in *__GI___libc_free (mem=<optimized out>) at malloc.c:3738
#5  0x00000000004006b0 in log_free (ptr=0x601010) at naming-test.c:34
#6  0x0000000000400788 in clist_delete (clist_head=0x601010) at naming-test.c:77
#7  0x00000000004007d1 in test_clist_create () at naming-test.c:85
#8  0x00000000004007e4 in main () at naming-test.c:90

while running different_names gives

$ ./different_names 
+0xb88010
-0xb88010
not crashing anymore!!

valgrind has some invalid accesses for same_names:

$ valgrind ./same_names
==8707== Memcheck, a memory error detector
==8707== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==8707== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==8707== Command: ./same_names
==8707== 
+0x51b9040
==8707== Invalid write of size 8
==8707==    at 0x40071B: clist_create (naming-test.c:60)
==8707==    by 0x4007B9: test_clist_create (naming-test.c:83)
==8707==    by 0x4007E3: main (naming-test.c:90)
==8707==  Address 0x51b9048 is 0 bytes after a block of size 8 alloc'd
==8707==    at 0x4C28BED: malloc (vg_replace_malloc.c:263)
==8707==    by 0x400653: log_alloc (naming-test.c:25)
==8707==    by 0x400700: clist_create (naming-test.c:53)
==8707==    by 0x4007B9: test_clist_create (naming-test.c:83)
==8707==    by 0x4007E3: main (naming-test.c:90)
==8707== 
==8707== Invalid write of size 8
==8707==    at 0x400727: clist_create (naming-test.c:61)
==8707==    by 0x4007B9: test_clist_create (naming-test.c:83)
==8707==    by 0x4007E3: main (naming-test.c:90)
==8707==  Address 0x51b9050 is 8 bytes after a block of size 8 alloc'd
==8707==    at 0x4C28BED: malloc (vg_replace_malloc.c:263)
==8707==    by 0x400653: log_alloc (naming-test.c:25)
==8707==    by 0x400700: clist_create (naming-test.c:53)
==8707==    by 0x4007B9: test_clist_create (naming-test.c:83)
==8707==    by 0x4007E3: main (naming-test.c:90)
==8707== 
==8707== Invalid write of size 8
==8707==    at 0x400737: clist_create (naming-test.c:62)
==8707==    by 0x4007B9: test_clist_create (naming-test.c:83)
==8707==    by 0x4007E3: main (naming-test.c:90)
==8707==  Address 0x51b9058 is not stack'd, malloc'd or (recently) free'd
==8707== 
==8707== Invalid write of size 8
==8707==    at 0x400743: clist_create (naming-test.c:63)
==8707==    by 0x4007B9: test_clist_create (naming-test.c:83)
==8707==    by 0x4007E3: main (naming-test.c:90)
==8707==  Address 0x51b9060 is not stack'd, malloc'd or (recently) free'd
==8707== 
==8707== Invalid read of size 8
==8707==    at 0x40076F: clist_delete (naming-test.c:76)
==8707==    by 0x4007D0: test_clist_create (naming-test.c:85)
==8707==    by 0x4007E3: main (naming-test.c:90)
==8707==  Address 0x51b9048 is 0 bytes after a block of size 8 alloc'd
==8707==    at 0x4C28BED: malloc (vg_replace_malloc.c:263)
==8707==    by 0x400653: log_alloc (naming-test.c:25)
==8707==    by 0x400700: clist_create (naming-test.c:53)
==8707==    by 0x4007B9: test_clist_create (naming-test.c:83)
==8707==    by 0x4007E3: main (naming-test.c:90)
==8707== 
==8707== Invalid read of size 8
==8707==    at 0x40077B: clist_delete (naming-test.c:77)
==8707==    by 0x4007D0: test_clist_create (naming-test.c:85)
==8707==    by 0x4007E3: main (naming-test.c:90)
==8707==  Address 0x51b9060 is not stack'd, malloc'd or (recently) free'd
==8707== 
-0x51b9040
not crashing anymore!!
==8707== 
==8707== HEAP SUMMARY:
==8707==     in use at exit: 0 bytes in 0 blocks
==8707==   total heap usage: 1 allocs, 1 frees, 8 bytes allocated
==8707== 
==8707== All heap blocks were freed -- no leaks are possible
==8707== 
==8707== For counts of detected and suppressed errors, rerun with: -v
==8707== ERROR SUMMARY: 6 errors from 6 contexts (suppressed: 4 from 4)

but not for different_names:

$ valgrind ./different_names
==11207== Memcheck, a memory error detector
==11207== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==11207== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==11207== Command: ./different_names
==11207== 
+0x51b9040
-0x51b9040
not crashing anymore!!
==11207== 
==11207== HEAP SUMMARY:
==11207==     in use at exit: 0 bytes in 0 blocks
==11207==   total heap usage: 1 allocs, 1 frees, 40 bytes allocated
==11207== 
==11207== All heap blocks were freed -- no leaks are possible
==11207== 
==11207== For counts of detected and suppressed errors, rerun with: -v
==11207== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)

What is causing this behaviour?

Can it be fixed but keeping the same names?

Était-ce utile?

La solution

Although it's legal to use the same identifier for both a type and a variable, it's not a good idea. It's confusing for anyone reading the code, and it can be confusing for you, too, because it creates some ambiguities.

One of these is:

sizeof(clist)   // line 54

If clist is both a type and a variable name, which one is used for the sizeof? [1] It matters because the variable clist is of type clist* and so the size of the variable is quite a bit less than the size of the struct the variable points at; you need to allocate enough space for the struct, which contains three pointers and two function pointers, and you are only allocating enough space for a single pointer. Subsequent writes into the struct's members will overwrite random memory.

In this case, you can force sizeof to return the struct's size by being explicit:

sizeof(struct clist)

But personally I would change the typedef name to something which doesn't create name collisions, like CList.


Note 1: The answer is that in this case, the variable wins, because it is declared in a more local scope. Both the typedef name and the variable name are in the same namespace, so the variable declaration in the inner scope "hides" the typedef name, but only in contexts in which both uses would be possible.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top