0

Please note: this kind of question has been asked on SO, but I believe mine is different, since the solution alone of using partial struct X* types doesn't solve this. I have read the other answers and have found no solution, so please bear with me.

I have the following circular-dependency problem (resulting in a compile error) which I'm unable to solve.

value.h relies on chunk.h, which relies on value_array.h, which relies on value.h.

Originally - chunk.h used to rely directly on value.h as well. I have managed to eliminate that dependency by having addConstant take a struct Value* instead of Value.

But I still haven't figured out how I can remove the dependency between value_array.h and value.h. The problem is that functions in value_array.c need to know the sizeof(Value), and so their signature can't take the partial type struct Value*.

Suggestions would be welcome.

The slightly simplified code:

value.h

#ifndef plane_value_h
#define plane_value_h

#include "chunk.h"

typedef enum {
    VALUE_NUMBER,
    VALUE_BOOLEAN,
    VALUE_CHUNK
} ValueType;

typedef struct Value {
    ValueType type;
    union {
        double number;
        bool boolean;
        Chunk chunk;
    } as;
} Value;

#endif

chunk.h

#ifndef plane_chunk_h
#define plane_chunk_h

#include "value_array.h"

typedef struct {
    uint8_t* code;
    ValueArray constants;
    int capacity;
    int count;
} Chunk;

void initChunk(Chunk* chunk);
void writeChunk(Chunk* chunk, uint8_t byte);
void setChunk(Chunk* chunk, int position, uint8_t byte);
void freeChunk(Chunk* chunk);
int addConstant(Chunk* chunk, struct Value* constant);

#endif

value_array.h

#ifndef plane_value_array_h
#define plane_value_array_h

#include "value.h"

typedef struct {
    int count;
    int capacity;
    Value* values;
} ValueArray;

void value_array_init(ValueArray* array);

void value_array_write(ValueArray* array, Value value);

void value_array_free(ValueArray* array);

#endif

value_array.c

#include "value_array.h"

void value_array_init(ValueArray* array) {
    array->values = NULL;
    array->count = 0;
    array->capacity = 0;
}

void value_array_write(ValueArray* array, Value value) {
    if (array->count == array->capacity) {
        int oldCapacity = array->capacity;
        array->capacity = GROW_CAPACITY(oldCapacity);
        array->values = reallocate(array->values, sizeof(Value) * oldCapacity, sizeof(Value) * array->capacity, "Dynamic array buffer");
    }

    array->values[array->count++] = value;
}

void value_array_free(ValueArray* array) {
    deallocate(array->values, array->capacity * sizeof(Value), "Dynamic array buffer");
    value_array_init(array);
}
alk
  • 69,737
  • 10
  • 105
  • 255
Aviv Cohn
  • 15,543
  • 25
  • 68
  • 131
  • If Chunk.c doesn't explicitly need to know type of Value, you could leave it as a void* and access its members directly through offsets. But someone stated move `void value_array_write(ValueArray* array, Value value);` to another file and that's probably your best bet – Irelia Aug 24 '19 at 11:45
  • Do not get hung up on the specific organization of your declarations into various headers -- although splitting things up in such a way is useful, your issue does not revolve around that. *Do* appreciate that structure types can be forward-declared, provided that you give them tags. The full definition of such a type does not need to be known for declaration of pointers to that type or for using that type in function prototypes. – John Bollinger Aug 24 '19 at 12:28

2 Answers2

0

ValueArray does not have to know what a value is because it contains only a pointer, which is opaque. Do not include Value.h from ValueArray.h

One question is why you are doing this when there are template classes like vector in C++ that will do this for you, but if you are determined to write your own list in C, then

#pragma once
struct Value;

typedef struct {
    int count;
    int capacity;
    struct Value* values;
} ValueArray;

void value_array_init(ValueArray* array);
void value_array_write(ValueArray* array, const Value* value);
void value_array_free(ValueArray* array);




#include "value_array.h"
#include "value.h"

void value_array_init(ValueArray* array) {
    array->values = NULL;
    array->count = 0;
    array->capacity = 0;
}

void value_array_write(ValueArray* array, const Value* value) {
    if (array->count == array->capacity) {
        int oldCapacity = array->capacity;
        array->capacity = GROW_CAPACITY(oldCapacity);
        array->values = reallocate(array->values, sizeof(Value) * oldCapacity, sizeof(Value) * array->capacity, "Dynamic array buffer");
    }

    array->values[array->count++] = value;
}

void value_array_free(ValueArray* array) {
    deallocate(array->values, array->capacity * sizeof(Value), "Dynamic array buffer");
    value_array_init(array);
}

In other words, you only think this isn't a duplicate of the other questions, the answer is, of course it is. The only other possibility was that you had a recursive definition in which one object included the others, in which case your code would be impossible.

You do not want to write a function passing Value by value. No, it won't work given your circular definition, and also it's slow. Why would you want to copy a large object on the stack? Pass it by reference.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Dov
  • 8,000
  • 8
  • 46
  • 75
  • Hi Dov, please see my edit adding the implementation of `value_array.c`. In it you can see I have to use `sizeof(Value)`, which complicates things. Hence this question. :) – Aviv Cohn Aug 24 '19 at 11:31
  • Just pointing out that writing your own list data structure is not a great idea – Dov Aug 24 '19 at 11:31
  • 1
    The implementation can include Value.h, that is not circular – Dov Aug 24 '19 at 11:33
  • @Dov, yeah, but the signature for `value_array_write` must specify `Value`, because its signature has to match the one in the implementation file. Am I wrong? – Aviv Cohn Aug 24 '19 at 11:33
  • I said to use a pointer, not by value, that solves one problem. Then include the definition value.h that solves the other problem – Dov Aug 24 '19 at 11:36
  • I just added the include to the source file in my answer since you don't seem to understand – Dov Aug 24 '19 at 12:19
  • You're quite right, @Dov, that the question is a dupe. That being the case, however, the appropriate action would be to VTC as a dupe. Nevertheless, this answer is mostly right, though it does overlook the fact that it is not necessary to convert the structure parameter in the function prototype to a pointer. – John Bollinger Aug 24 '19 at 12:33
0

Change this:

void value_array_write(ValueArray* array, Value value);

to this:

void value_array_write(ValueArray* array, const Value* value);

or move this:

void value_array_write(ValueArray* array, Value value);

to another header file (put methods and structures into separate header files).

S.S. Anne
  • 15,171
  • 8
  • 38
  • 76
hehaoqian
  • 166
  • 6