0

I'm trying to implement my own assert macro in a C89 standard.

I want it to be exactly as the original one:

dir/file.c:20: MyFunction: Assertion `null != pointer` failed.

There are 2 problems:

  1. There is no option to print the function name because the pre identifier __FUNC__ is available only since c99 standard.
  2. I don't know how to exit the program. I tried exit(1) and __Exit(1) but both of them are not working and I think it's because macros are converted to code while the per-processing stage, which means the pre-processor doesn't even know what are these exit functions yet. because they are relevant only in the compiler stage, right?

Here's my code:

/********************************* Inclusions *********************************/
#include <stdio.h>  /* printf, NULL */

/***************************** Macros Definitions *****************************/
#define ASSERT(expr) \
    if (!(expr)){ \
        fprintf(stderr, "%s:%d: Assertion `%s` failed.\n" \
                ,__FILE__, __LINE__, #expr); }

/******************************************************************************/
int main()
{
    void *null_ptr = NULL;
    
    ASSERT(NULL != null_ptr);
    
    printf("ALL WORKS");
    
    return (0);
}

/******************************************************************************/

my output is:

`file.c:25: Assertion `NULL != null_ptr` failed.`

Is there any way to get the function name or exit the program with a macro? Because right now, I'm not getting the function's name, and more important, the program isn't getting stopped even though the assert prints an error message.

And it's strange, because how it's not possible to get the function name or to exit a program with a macro but it is possible for the original assert to do both of these?

P.S the __FILE__ per-identifier prints for me only the file name, as file.c and not dir/file.c as the original assert does. Why is that?

I wish I could write something like:

#define ASSERT(expr) \
        if (!(expr)){ \
            fprintf(stderr, "%s:%d: %s: Assertion `%s` failed.\n" \
                    ,__FILE__, __LINE__, __FUNC__, #expr); exit(1) }

Thanks.

NoobCoder
  • 513
  • 3
  • 18
  • 2
    The `exit` in your macro should work if you add a semi-colon after it and `#include ` – EdmCoff May 06 '21 at 14:59
  • 1
    The preprocessor does not need to care about what `exit()` means. It just has to put that function call into the code. The compiler and linker will take care about it. – Gerhardh May 06 '21 at 14:59
  • 1
    "I want it to be exactly as the original one:" What is "the original one"? Where do you get that output? Also "but both of them are not working" What does "not working" mean? What happens instead? – Gerhardh May 06 '21 at 15:01
  • @Gerhardh The original one is the original built in C `assert` macro. An example for its output is mentioned right in the beginning of the post. My custom `assert` prints the same output just without the `function name`. – NoobCoder May 06 '21 at 15:04
  • @NoobCoder I'm not sure if there's any "original way" or that the message has to be exactly that. For me, I get `a.out: main.c:10: main: Assertion '0' failed.\nAborted (core dumped)` – mediocrevegetable1 May 06 '21 at 15:07
  • I know that there is an example. That is why I asked where you got that from. ;) You mean the `assert` macro of the compiler that you are using? Then you can just look into the `assert.h` header file to see how this output is created and how the program is terminated. – Gerhardh May 06 '21 at 15:09
  • 2
    `assert` is not required to print the function name. It needs to print the file name and the line number and the text of the argument, in an implementation-defined manner. It is then required to call `abort` (not `exit`). – n. m. could be an AI May 06 '21 at 15:17
  • @n.'pronouns'm. Thanks for your answer. Is there any reason to prefer `abort` over `exit` in terms of time/memory/security/something else? – NoobCoder May 06 '21 at 15:23
  • 1
    Note that using `if` in a macro this way is buggy; if the programmer writes `if (cond) ASSERT(expr); else other_stuff();` it will not behave like they expect. This is why the [do while hack](https://stackoverflow.com/questions/154136/why-use-apparently-meaningless-do-while-and-if-else-statements-in-macros) is needed. However, as @mediocrevegetable1 says in their answer, your macro should expand to an expression anyway, so you need to use `? :` instead of `if`. – Nate Eldredge May 06 '21 at 16:12
  • The standard says it calls `abort`. There is no wiggle room here. – n. m. could be an AI May 06 '21 at 16:59

2 Answers2

3
  1. Indeed, C89 doesn't have a way to get the function name. So if you can only rely on C89, you'll have to do without this. Note that many implementations may have provided their own extensions for this even before C99, and may have used those extensions in their own definitions of assert(); e.g. GCC had __FUNCTION__.

  2. The standard assert() macro calls abort() if the assertion fails. So if you want to replicate its behavior, you can do the same.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • Thanks for your answer. For my `P.S` part, is there any idea why this is happening? Why would the standard `assert` print the name of the file as `dir/file.c` and when I use `__FILE__` it prints only the name of the file without the dir. Only `file.c` ? – NoobCoder May 06 '21 at 15:25
  • Is there any reason to prefer abort over exit in terms of time/memory/security/something else? – NoobCoder May 06 '21 at 15:25
  • @NoobCoder: The C standard (which would be good to read if you are trying to follow it so closely!) only says that `__FILE__` has an implementation-defined format; whether it includes a path is up to the implementation. And it is entirely possible that `assert()` uses some implementation-specific magic that is different from what that implementation does for `__FILE__`. If you can't use anything beyond what the standard provides, you can't do anything about that. – Nate Eldredge May 06 '21 at 15:43
  • @NoobCoder: That said, on my system, both `__FILE__` and `assert()` include the path as it was passed to the compiler, so `gcc foo.c` and `gcc /dir/foo.c` result in different values. – Nate Eldredge May 06 '21 at 15:43
  • 2
    @NoobCoder: The main difference between `abort()` and `exit()` is that `abort()` often behaves in a way that is more useful for debugging. For example, on Unix, it can create a core dump if the user or sysadmin has enabled them, whereas `exit()` never does. There are some other differences as mentioned in the link; e.g. `abort()` does not call `atexit()` functions (which is usually a good thing if the program is already known to be in an impossible state), and it may or may not flush open files. – Nate Eldredge May 06 '21 at 15:45
  • 1
    @NoobCoder: As a general principle, you shouldn't necessarily expect to be able to exactly replicate one part of the C standard using other parts. Indeed, many pieces of functionality are provided by the standard precisely because they can't be implemented in terms of the rest. – Nate Eldredge May 06 '21 at 15:46
2

Nate Eldredge answers most of your queries. In response to your P.S., I suspect it's something the compiler can internally do that we can't. Same with the function name but no __func__ (though GCC has a __FUNCTION__ macro that you can use).

You can still make your assert closer to the compiler assert though. That assert tries to emulate a function as best as possible. So for one, it has to work as an expression, which yours does not because of the if. Furthermore, it should return void. The man page on assert gives this "prototype":

void assert(scalar expression);

Both of these are possible. Here's an assert I just made right now that (I think) manages to meet both these requirements:

#define ASSERT(expr)                                                        \
    ((expr) ?                                                               \
        (void) 0 :                                                          \
        (void) (fprintf(stderr, "%s:%d: %s: Assertion `%s` failed\n",       \
                        __FILE__, __LINE__, __FUNCTION__, #expr), abort()))

This makes use of the __FUNCTION__ GCC extension, you can remove that if you want to.

mediocrevegetable1
  • 4,086
  • 1
  • 11
  • 33
  • Thanks a lot for your elaborated answer. Everything you wrote works but the `__func__`. neither `__FUNCTION__`. I'm compiling with GCC extension but it still does not work. `ISO C does not support ‘__FUNCTION__’ predefined identifier [-Wpedantic]` – NoobCoder May 06 '21 at 16:36
  • This is how I compile it: `gcc -ansi -pedantic-errors -Wall -Wextra -g` – NoobCoder May 06 '21 at 16:37
  • @NoobCoder like I said, `__FUNCTION__` is an extension, not standard C. Due to your `-pedantic-errors`, the compiler will throw errors when you try to do anything that violates the standard. Since `__FUNCTION__` isn't part of the standard, an error is thrown. And since `__func__` was introduced in C99, it doesn't exist in C89, so again, an error. – mediocrevegetable1 May 06 '21 at 16:38