2

I have a program that compiles on both MacOS and Linux. In my makefile, I define a variable:

# MAC
ifeq ($(UNAME), Darwin)
OS          = APPLE
    
#LINUX
else
OS          = LINUX
endif
    
INCLUDES    = -Iincludes -Ilibft -I$(MLX_DIR) -D$(OS)

Leading to the following compilation:

gcc -Wall -Wextra -Werror -O3 -Iincludes -Ilibft -I./minilibx_mms -DAPPLE -c srcs/parser/parser.c -o objs/parser/parser.o
gcc -Wall -Wextra -Werror -O3 -Iincludes -Ilibft -I./minilibx_mms -DAPPLE -c srcs/terminate/gameover_sys.c -o objs/terminate/gameover_sys.o

As you can see, I am passing APPLE as a macro so when I call this function:

    if (cub->mlx)
    {
        #ifdef LINUX
        mlx_destroy_display(cub->mlx);
        #endif
        free(cub->mlx);
    }

Everything between the #ifdef and #endif should be removed before compilation, right? But alas, I am getting this error from the compiler:

Call to undeclared function 'mlx_destroy_display'; ISO C99 and later do not support implicit function declarations clang(-Wimplicit-function-declaration)

The function only exists in the Linux implementation of the library. Where is my understanding of preprocessor directives incorrect? To my understanding the whole #ifdef LINUX part should be removed when the LINUX macro is not present.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
Hendrik
  • 175
  • 8
  • 2
    Maybe one of your included headers defines that macro? – Gerhardh Feb 23 '23 at 09:23
  • Hi Gerhardh! I do notice now in a header I have this: /* OS CHECK */ # ifdef APPLE # define LINUX 0 # define ESC_KEY 53 However, I thought defining it to 0 would mean it is undefined. Apparently that is not the case? – Hendrik Feb 23 '23 at 09:26
  • 2
    @Hendrik No. Definining it t`o` 0 means it is **defined** for the purposes of `#ifdef/#ifndef/#if defined(Macro)` checks. But if you do `#if LINUX` instead, then both an undefined and a 0-defined LINUX will cause that `#if` branch to be deleted. (That's why I personally prefer `#if` over `#ifdef`). – Petr Skocik Feb 23 '23 at 09:28
  • 1
    Thanks all. You are right, a simple #if LINUX instead of #ifndef LINUX sufficed. This seems so obvious now in hindsight. – Hendrik Feb 23 '23 at 09:30
  • @Hendrik It might not be super obvious. The preprocessor replaces undefined tokens with 0 in its `#if` conditionals. So `#if some_undefined+3 == 3` is a taken branch. That's why sometimes you see casts like `((int)+OTHER_MACRO)` macro definitions so that those macros are also usable in `#if` conditionals where they become `((0)+something)`. – Petr Skocik Feb 23 '23 at 09:34
  • You must distinguish using `#ifdef XY` from `#if XY`. They behave different if you define to 0. – Gerhardh Feb 23 '23 at 09:53

2 Answers2

2

How to fix a compiler error that should be excluded by a preprocessor directive between #ifdef?

LINUX is very simple common name, and standard headers or other projects may already define it. Use a unique prefix of macros for your project.

# Makefile
ifeq ($(UNAME), Darwin)
OS = APPLE
else
OS = LINUX
endif
CFLAGS = -Iincludes -Ilibft -I$(MLX_DIR) -DYOUR_LIBRARY_NAME_OS_$(OS)

# C file
#ifdef YOUR_LIBRARY_NAME_OS_LINUX
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
0

Another option:

Check for a reserved symbol such as __linux__ or __MACH__ instead of defining your own.

From GCC's online predefined macros documentation (N.B. the bolded portions):

The C standard requires that all system-specific macros be part of the reserved namespace. All names which begin with two underscores, or an underscore and a capital letter, are reserved for the compiler and library to use as they wish. However, historically system-specific macros have had names with no special prefix; for instance, it is common to find unix defined on Unix systems. For all such macros, GCC provides a parallel macro with two underscores added at the beginning and the end. If unix is defined, __unix__ will be defined too. There will never be more than two underscores; the parallel of _mips is __mips__.

When the -ansi option, or any -std option that requests strict conformance, is given to the compiler, all the system-specific predefined macros outside the reserved namespace are suppressed. The parallel macros, inside the reserved namespace, remain defined.

We are slowly phasing out all predefined macros which are outside the reserved namespace. ...

For example:

$ echo | gcc -dM -E - | grep -i linux
#define __linux 1
#define __linux__ 1
#define __gnu_linux__ 1
#define linux 1

versus with -std=c11:

$ echo | gcc -std=c11 -dM -E -  | grep -i linux
#define __linux 1
#define __linux__ 1
#define __gnu_linux__ 1

By using a reserved symbol, you also have protection from name collisions with third-party headers.

Also, see How do I check OS with a preprocessor directive?

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56