Using if directives in headers

93 Views Asked by At

I have a working generic graph. The type for vertex is currently defined as:

typedef struct vertex {
  void *data;
  char *label;
  bool inGraph;
} vertex;

The void pointer can then be customized for any algorithm, like BFS, DFS, etc. This works, but I want to simplify the code by using a separate header file for a vertex that specifies its type as needed, and is included in graph.h. Something like this:

#ifndef VERTEX_H_INCLUDED
#define VERTEX_H_INCLUDED

#include <stdlib.h>
#include <stdbool.h>

#if defined (GRAPH_VERTEX1)
typedef struct vertex {
  struct vertex *parent;      
  char *label;        
  bool inGraph;       
} vertex;

#elif defined (GRAPH_VERTEX2)
typedef struct vertex {
  size_t dist;    
  char *label;        
  bool inGraph;       
} vertex;

#else  // default
typedef struct vertex {   
  char *label;        
  bool inGraph;       
} vertex;

#endif

#endif

This does not work, however. The right vertex type is not selected and allocated.

I have made an example to reproduce the issue.

main.c

#include <stdio.h>

#define GRAPH_VERTEX1

#include "graph.h"

int main() {
  vertex *v = createVertex("test");
  v->inGraph = true;
  vertex *parent = createVertex("parent");

  printf("Vertex: %s\n", v->label);
  printf("Parent: %s\n", parent->label);
  printf("In graph: %d\n", parent->inGraph);

  freeVertex(v);
  freeVertex(parent);
  return 0;
}

graph.h

#ifndef GRAPH_H_INCLUDED
#define GRAPH_H_INCLUDED

#include <stdlib.h>

#include "vertex.h"


vertex *createVertex(char *label);

void freeVertex(vertex *v);


#endif

graph.c

#include "graph.h"
#include <string.h>

vertex *createVertex(char *label) {
  vertex *v = calloc(1, sizeof(vertex));
  v->label = calloc(strlen(label) + 1, sizeof(char));
  v->inGraph = false;
  strcpy(v->label, label);
  return v;
}

void freeVertex(vertex *v) {
  free(v->label);
  free(v);
}

The example works fine if I put a single definition in vertex.h. However, if I try to use the if directives, I get the following errors:

==986== Memcheck, a memory error detector
==986== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==986== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==986== Command: ./a.out
==986==
==986== Invalid write of size 1
==986==    at 0x1090FD: main 
==986==  Address 0x4a8e050 is 0 bytes after a block of size 16 alloc'd
==986==    at 0x484DA83: calloc 
==986==    by 0x109279: createVertex 
==986==    by 0x1090F5: main 
==986==
==986== Invalid read of size 1
==986==    at 0x10913A: main 
==986==  Address 0x4a8e0f0 is 0 bytes after a block of size 16 alloc'd
==986==    at 0x484DA83: calloc 
==986==    by 0x109279: createVertex 
==986==    by 0x109108: main 
==986==
Vertex: (null)
Parent: (null)
In graph: 0
==986== 
==986== HEAP SUMMARY:
==986==     in use at exit: 0 bytes in 0 blocks
==986==   total heap usage: 5 allocs, 5 frees, 4,140 bytes allocated
==986==
==986== All heap blocks were freed -- no leaks are possible
==986==
==986== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==986==
==986== 1 errors in context 1 of 2:
==986== Invalid read of size 1
==986==    at 0x10913A: main 
==986==  Address 0x4a8e0f0 is 0 bytes after a block of size 16 alloc'd
==986==    at 0x484DA83: calloc 
==986==    by 0x109279: createVertex 
==986==    by 0x109108: main 
==986==
==986==
==986== 1 errors in context 2 of 2:
==986== Invalid write of size 1
==986==    at 0x1090FD: main 
==986==  Address 0x4a8e050 is 0 bytes after a block of size 16 alloc'd
==986==    at 0x484DA83: calloc 
==986==    by 0x109279: createVertex 
==986==    by 0x1090F5: main 
==986==
==986== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
2

There are 2 best solutions below

0
Ted Lyngmo On BEST ANSWER

graph.c needs to have the same #define GRAPH_VERTEX1 as you have in main.c - otherwise that translation unit will get the default vertex definition (since neither GRAPH_VERTEX1 nor GRAPH_VERTEX2 will be defined).

You could put #define GRAPH_VERTEX1 in a separate header file that you include from both main.c and graph.c:

#ifndef SOMENAME_H
#define SOMENAME_H

#define GRAPH_VERTEX1
#include "graph.h"

#endif

... or just put it at the top of graph.h.

Alternatively, tell your compiler to define it from the command line. Example:

gcc -DGRAPH_VERTEX1 ...
0
Fe2O3 On

You have not indicated the extent of the code that would process the three "flavours" of struct vertex. It may well be that functions serving all three operations could clearly and safely be coded in a single source file. It may well be that separating declarations and functions into individual files would impede a reader's understanding.

On that theme, here is an alternative proposal to declare the struct central to this project.

// Uncomment one of these when an augmented version required
// #define GRAPH_VERTEX1
// #define GRAPH_VERTEX2

typedef struct vertex {
    char *label; // common to all
    bool inGraph; // common to all

#if defined (GRAPH_VERTEX1)
    struct vertex *parent;

#elif defined (GRAPH_VERTEX2)
    size_t dist;

#endif
} vertex;

Should there be a need for additional members to serve some distinct purpose(s), there are other ways to keep things minimal, yet well defined.

OT: The pointer vertex.label will (likely) require 8 bytes (to point to a region of heap.) The heap allocator (apart from its processing cycles) will require (likely) 8-16 more bytes of overhead, and will return the address of a block whose actual size will be consistent with required alignment (likely a multiple of 8 bytes.) The 4+1 bytes of the value "test" will, therefore, consume (probably) 32+ bytes of memory. If it fits your application, restricting the label to char label[ 15 + 1 ]; would both reduce complexity and increase processing speed. Food for thought...

PS: I recommend against burying #include <stdxxx.h> inside application header files. A reader seeing fread() in the file "foo.c" should feel confident that #include <stdio.h> appears at the top of this source file. Only use #include in a header file when there is an application specific hierarchy involved. Dependencies can, very quickly, become tangled and incomprehensible.