0

I'm trying to implement a stack using a fixed-length array. I want to create multiple instances of Stack, so my stack implementation is in my stack.h header.

stack.h


#ifndef STACK_H
#define STACK_H

typedef int Item;

typedef struct stack{
        Item array[SIZE];
        int current_size;
}Stack;

Stack *create_stack(void);
void push_stack(Stack *stack, Item value);
Item pop_stack(Stack *stack);

#endif

However, I want to keep the SIZE of the array private, so I have it defined in stack.c

stack.c

#include "stack.h"

#define SIZE 100

Stack *create_stack(void)
{
    ....
}

void push_stack(Stack *stack, Item value)
{
    ....
}

Item pop_stack(Stack *stack)
{
    ....
}

Would this be possible? Have a macro defined in the source file but used in a header file? Thanks

Pablo
  • 13,271
  • 4
  • 39
  • 59
Jules
  • 423
  • 3
  • 11
  • Hmmm, perhaps a _flexible array member_ ? `typedef struct stack{ int current_size; Item array[]; }Stack;`, but why not just use `typedef struct stack{ int current_size; Item *array; }Stack;` ? Jules, why do you want an _array_ in `Stack` of unknown public size? The answer to that can lead to a better approach. – chux - Reinstate Monica Nov 09 '19 at 03:39
  • the homework stipulation is that the stack must use a fixed-length array. – Jules Nov 09 '19 at 03:44
  • Jules, what you want is an [opaque pointer](https://stackoverflow.com/q/7553750/2410359) and never show the `typedef struct stack{ Item array[SIZE]; int current_size; }Stack;` to the public. Let all access to `Stack` occur through `Stack*` and functions. – chux - Reinstate Monica Nov 09 '19 at 03:53

2 Answers2

1

Technically, you should consider the #include pre-processor instruction as a "copy and paste" instruction.

This basically means that the end-result is a single file containing all the text in the order specified.

Of course, in that case, you have an error in you code, since SIZE is only defined after it's used.

Consider, in stack.c:

#define SIZE 100
#include "stack.h"

This would allow SIZE to be used within stack.h.

Also, by not defining SIZE in the header (at least as a fallback value), you will not be able to include the header anywhere without manually defining SIZE first.

Consider, in the header, adding:

#ifndef SIZE
#define SIZE 10
#endif

IMHO, this approach isn't the ideal way to use this capability, but this should answer your question.

The best approaches would, IMHO, be to use an incomplete type or a "flexible array".

For example, you could define the type using:

typedef struct stack{
        int current_size;
        Item array[];
} Stack;

Then allocate enough memory to manage the stack size using (for example):

Stack * s = malloc(sizeof(*s) + (SIZE*sizeof(Item)));

However, I would probably opt for making the type an opaque pointer.

This means that data will only be accessible using the functions.

Using an opaque pointer would provide much stronger compartmentalization.

It's also helpful by allowing you to update the data type in the future without breaking ABI / backwards compatibility.

i.e., in the header limit yourself to:

typedef struct stack Struct;

The functions will use a pointer to an incomplete type.

Stack *create_stack(void);

This will promise that no one using the header could access the data (they will get an error about accessing an incomplete type).

The C file will define the struct only for it's internal use:

struct stack{
        Item array[SIZE];
        int current_size;
};

Or, using a dynamic size approach, as noted above:

struct stack{
        int current_size;
        Item array[];
};


Stack *create_stack(size_t size)
{
    Stack * s = malloc(sizeof(*s) + (size * sizeof(*s->array)));
    // ....
    return s;
}

P.S. - IMHO

As a note about naming... you seem to be using a CamelCase naming convention.

In C, you would find that most developers tend to name things using snake_case rather than CamelCase.

Also, typedefs often have a suffix, similar to the (reserved) _t suffix used by POSIX and some standard types (i.e., struct types might end with _s).

The are only my personal opinions, but as a long time code reader, I find it easier to read code that matches the style of the standard library provided by a language. Obviously, not everyone agrees with me.

Using these naming conventions, I would name these types as:

typedef int item_i;

typedef struct stack{
        int current_size;
        item_i array[SIZE];
} stack_s;
Myst
  • 18,516
  • 2
  • 45
  • 67
  • Using CamelCase, underscores, of various suffixes are practices of particular institutions or groups or are personal preferences. They are not “C conventions,” “Java conventions,” or “C++ conventions.” – Eric Postpischil Nov 09 '19 at 11:28
  • @EricPostpischil - conventions are just what the community most often chooses. No one (in most languages) will stop you from using whichever style you wAnT ;) ... however, these styles are associated with specific languages and they do have a history. [snake_case](https://en.wikipedia.org/wiki/Snake_case) is associated with C, as seen by the style of the standard library, POSIX and the Linux kernel. If I mentioned the Pascal language style (as in PascalCase) I might not have been understood, but Java and C++ are also associated with PascalCase... hmm, sorry, CamelCase. – Myst Nov 09 '19 at 11:54
  • Associations with specific languages are weak. They are not “C conventions,” “Java conventions,” or “C++ conventions.” It’s a waste and a distraction to be polluting students’ minds with prejudices about naming styles. – Eric Postpischil Nov 09 '19 at 11:59
  • In any case, a good answer would focus on the semantics of defining structures and complete versus incomplete object types, not on mechanics about source file inclusion. That is a key concept—what the compiler knows and needs to know while it is compiling one translation unit. It **needs** to know the size of the array in a structure to be able to operate on the structure (copy/assign it, create instances, and more). Header files and macro definitions are minor actors compared to this. – Eric Postpischil Nov 09 '19 at 12:05
  • @EricPostpischil - I updated the answer to clarify that this is just my opinion and to remove the explicit naming of conventions by language. On the rest of the stuff, we might simply disagree. – Myst Nov 09 '19 at 12:06
  • Additionally, the use of a flexible array member is problematic. If the OP is struggling with thinking they need the array size in clients while they want to keep it private to the implementation, it means they have not learned techniques for dealing with this—they are writing code in the clients that expects a complete object. In that case, simply substituting a definition with a flexible array member will fail, as the structure will act like a complete object type with its base size, but the actual objects will be larger. – Eric Postpischil Nov 09 '19 at 12:07
  • Thanks for the explanation and the advice! – Jules Nov 09 '19 at 22:34
  • @Jules , you're welcome. I'm happy I could help. FYI, we normally avoid "thank you comments" on SO. By accepting the answer you said your thanks. If everyone wrote a "thank you" comment, the SO database would fill up with them to the brim ;-) ... so we just skip them. – Myst Nov 09 '19 at 22:37
-1

Yes, this is very possible. You just need to put th #define SIZE 100 line above the #include "stack.h" line. The C Pre-Processor will basically copy/paste the contents of the header file into the position it was included at, so if you define SIZE above where you include it, then it the contents of the header file will be put under it and compile just fine.

Side note: Depending on your set up, you might be able to run just the C Pre-Processor using cpp source.c and you would be able to see how the code gets put together before being sent to the compiler.