3

In the following fragment I have a struct IndexError that I return when the user made an error using my library. I have a function-like macro that casts a pointer to a IndexError*, and an enum, both called INDEX_ERROR.

enum errors {
    SUCCESS,
    INVALID_ARGUMENT,
    INDEX_ERROR
};

struct Error {
    char error_buff[BUFSIZ];
};

typedef struct Error Error;

struct IndexError {
    Error  parent;
    size_t invalid_index;
    // etc.
};

typedef struct IndexError IndexError;


#define INDEX_ERROR(obj) ((IndexError*) obj)

An example to how I would use this is:

size_t pos = 4;
int IndexPointer* error = NULL;
int status = array_remove_item(my_array, pos, &error);

Then I check the status. If it doesn't return SUCCESS, I would examine the error, because that should then point to a newly create error.

The implementation of one of the array functions might look like this:

int array_remove_item(Array* array, size_t pos, Error** error_out)
{
    Error* error = NULL;
    if(pos >= array->size) {
        index_error_create(INDEX_ERROR(&error), pos); // use casting macro.
        *error_out = error;
        return INDEX_ERROR; // is this the macro or the value from the errors enum?
    }
    priv_array_remove_item(array, pos);
    return SUCCESS;
}

So my question is, at the return INDEX_ERROR;, will the INDEX_ERROR return the value from the enum, or will the preprocessor bite me because my naming is inconvenient?

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
hetepeperfan
  • 4,292
  • 1
  • 29
  • 47
  • Macro replacement happens in a separate stage *before* other identifiers are passed through the compiler. [This translation phase reference](https://en.cppreference.com/w/c/language/translation_phases) could be helpful. – Some programmer dude Mar 14 '19 at 09:29
  • Macros get expanded _before_ compilation. As a result your enum value `INDEX_ERROR` will have been replaced with the macro text. However, I would expect the pre-processor to complain that you reference the macro without a parameter. – Paul Ogilvie Mar 14 '19 at 09:29
  • Not directly related, but identifiers of `_This _Sort` are reserved to the implementation for any use. You can declare `typedef struct Error Error;` just fine. – StoryTeller - Unslander Monica Mar 14 '19 at 09:30
  • Also note that in C (as well as C++, but in the future only tag the language you're actually programming in since C and C++ are otherwise two very different languages) symbols beginning with an underscore and followed by an upper-case letter (like e.g. `_Error`) are *reserved* in all scopes for the compiler and standard library. You should generally not define such symbols yourself. – Some programmer dude Mar 14 '19 at 09:30
  • @StoryTeller Editted and removed the underscores – hetepeperfan Mar 14 '19 at 09:32
  • I would think it is the enum, because otherwise I would `return (IndexError* obj)` where obj isn't specified. – hetepeperfan Mar 14 '19 at 09:38
  • This code is invoking lots of undefined behavior bugs. See for example [What is the strict aliasing rule?](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule). – Lundin Mar 14 '19 at 12:33
  • @lundin I'm using the techniques to achieve inheritance in C. See for example the answer of Adam Rosenfield in https://stackoverflow.com/questions/415452/object-orientation-in-c or the chapter on inheritance in this book Object-Oriented programming With ANSI-C www.cs.rit.edu/~ats/books/ooc.pdf . Also Libraries that work with GObject glib, gtk gstream etc use the same technique. – hetepeperfan Mar 15 '19 at 07:45
  • @hetepeperfan That's a very obscure way to do it. There are far better ways, I've written examples about it before on SO, can't find the post. I won't recommend Schriener's book, it is quite convoluted. That being said, neither the linked post nor the book suggest wild pointer conversions from arrays to struct as in your code. Likely because that leads to undefined behavior. – Lundin Mar 15 '19 at 07:57
  • `struct object {int refcount;}; struct derived{struct object;char*msg};` In here you can up cast a struct derived to its parent, whereas the opposite isn't true as for the same reasons a dynamic_cast<() might fail in C++. However, since the first member of `struct derived` is a `struct object`, the first `sizeof(struct object)` bytes are the same. – hetepeperfan Mar 15 '19 at 07:57
  • @hetepeperfan Yes because that particular struct hits an exception in the strict aliasing rule. There's also the union common initial sequence trick. But that doesn't mean you can go from `char error_buff[BUFSIZ];` to some entirely different struct! – Lundin Mar 15 '19 at 07:59

1 Answers1

7
return INDEX_ERROR; // is this the macro or the value from the errors enum?

It's the enumerator. It can't be the result of expanding the function-like macro, because it isn't followed immediately by a left paren ( token, as the preprocessor requires1.

It's slightly smelly, though. Different names for the macro and the enumerator will make the code clearer and self evident without needing to read the fine print of the language specification.


1 - n1570 6.10.3p10 "Each subsequent instance of the function-like macro name followed by a ( as the next preprocessing token introduces the sequence of preprocessing tokens that is replaced by the replacement list in the definition"

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458