2

I'm developing a C application that I would like to be reasonably portable. It builds ok with with gcc and clang on Linux, and with MSVC on Windows. After getting access to a Mac I tried building with the Command Line Tools.

It fails to compile because my code declares a function isnumber and Apple's ctype.h header also declares a (non standard?) isnumber. I can rename my function so it doesn't conflict, but is there a way to avoid this by disabling or ignoring all, or specific, Apple additions to standard headers? E.g. is there a compiler option or preprocessor pragma to ignore them?

My isnumber is unrealated to checking character classes. Below is code that reproduces the issue - it compiles with clang/Linux and MSVC/Windows but not on a Mac ( - it is not the actual code).

#include <ctype.h>
#include <stdio.h>

char *isnumber(void);

int main(void)
{
    char *opt = "A";

    if (isupper(*opt))
        printf("THE IS NUMBER IS: %s\n", isnumber());
    else
        printf("The IS number is: %s\n", isnumber());

    return 0;
}

char *isnumber(void)
{
    return "IS-123";
}

Error:

/Users/ ... /repro/main.c:4:7: error: conflicting types for 'isnumber'
char *isnumber(void);
      ^
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_ctype.h:323:1: note: previous definition is here
isnumber(int _c)

Update:

As Acorn's answer and it's comments describe, 'isnumber' is a BAD name for a function as the C11 standard reserves it:

7.31 Future library directions
The following names are grouped under individual headers for convenience. All external names described below are reserved no matter what headers are included by the program.

...

7.31.2 Character handling <ctype.h>
"Function names that begin with either is or to, and a lowercase letter may be added to the declarations in the <ctype.h> header".

So the 'correct' solution to my original problem is for me to rename my functions.

codybartfast
  • 7,323
  • 3
  • 21
  • 25
  • 1
    What is the problem to rename YOUR function? Maybe your project is 50,000 lines and you will need 30 minutes to replace all calls... – i486 Aug 12 '20 at 08:35
  • Have you tried compiling with `-std=c11`? – Felix G Aug 12 '20 at 08:47
  • @i486 1) I have a whole series of 'is...' functions that are consistenly named and match a published spec (nothing to do with C). So I could have an 'is_number' that inconsistent with all the other 'is...' functions or rename all the 'is...' functions so they are consistent with each other, (but inconsistent with the rest of the naming style). 2) I am able to rename but there may be others, who cannot easily do so. – codybartfast Aug 12 '20 at 08:48
  • 1
    @FelixG I've just tried -std= with each of c89, c99, c11 and c17 with the same result. – codybartfast Aug 12 '20 at 08:54
  • 2
    Did you look at the header file to see if there are any `#ifdefs` regarding those functions? – Felix G Aug 12 '20 at 08:55
  • 1
    @FelixG I did earlier, but looking again there may be hope... will try a few things and report back. – codybartfast Aug 12 '20 at 08:58
  • Try `-std=c11 -pedantic-errors`. – Lundin Aug 12 '20 at 11:05

2 Answers2

8

I'm developing a C application that I would like to be reasonably portable.

It fails to compile because my code declares a function isnumber

Both C and POSIX reserve all is[a-z]* names (in the case of POSIX only when including the header), so the code is not portable.

The only way to make it portable is to avoid using such an identifier.

A solution would be to prefix all the identifiers coming from that spec you mention with something that resembles the name of the spec, e.g. xx* or xx_*. A similar approach is taken by C libraries to avoid collisions with others.

Non-solutions include:

  • Avoiding to include ctype.h. Still not portable, even if in practice it has a higher chance of working in other systems.
  • Disabling extensions using some macro definition. Still not portable, since other systems may not recognize that and still define isnumber. In practice you will end up having to research how to do a similar thing in every system.
Acorn
  • 24,970
  • 5
  • 40
  • 69
  • This is a really useful information but I may accept my own answer as the specific question is about disabling Apple addtions which may have applications. E.g., someone doing primary development on Mac may wish to disable Apple addtions to avoid accidentally taking a dependency on Apple specific stuff. – codybartfast Aug 12 '20 at 09:55
  • 3
    Even if `` is not included, the `is[a-z]*` names are still reserved by the C standard (C11 7.31 and 7.31.2). – Ian Abbott Aug 12 '20 at 09:58
  • @IanAbbott You are right, thanks! I will add that too. – Acorn Aug 12 '20 at 10:03
  • Here's a link to the doc: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf :-) (or at least its draft) – codybartfast Aug 12 '20 at 10:07
  • I FULLY accept that the use of 'isnunber' is BAD... and I will edit the question to reflect this, and I will probably rename all the 'is...' and 'to...' functions that I have. I'm happy to do that when it's required by the language spec (not so happy because of a vendor's implementation). I'm glad I mentioned portability because of the very useful comments and answers, but the main question is *not* about portability nor good/valid function names. There may be other reasons why people might want to disable vendor additions. – codybartfast Aug 12 '20 at 10:13
  • @codybartfast (Reposted comment to edit) The only way to make your code actually portable is to avoid using such an identifier. Using an `#ifdef` does not make it portable -- I will clarify this. – Acorn Aug 12 '20 at 10:16
  • 1
    The key point is that disabling Apple extensions does not make the code portable. In fact, that is typically intended as an escape hatch when porting *non-portable software* coming from other systems, i.e. so that it works without changing the code. But that means the software is *less* portable, not more! – Acorn Aug 12 '20 at 10:23
  • Re “The only way to make it portable is to avoid using such an identifier”: It can be made portable by using the identifier only with internal linkage, i.e., declaring the function `static`. 7.31 is sloppy to use “external names,” as “external” is used by the C standard to refer to things declared outside of any function and to external linkage, but 7.1.3 makes it clear the reservation on these identifiers is for use with external linkage. – Eric Postpischil Aug 12 '20 at 11:34
  • @Eric: I don't think that's correct. "external name" is defined in 6.4.2.1/5 ("an identifier that has external linkage") and the phrase is used in several other places in the standard. 7.1.3 says that external library names are (1) reserved in file scope if the relevant header is included and (2) reserved for use with external linkage, always. But the names in 7.31 don't appear in any header. They are grouped by header "for convenience" but future standards might define these names in different headers. So 7.31 extends 7.1.3 by reserving these names in file scope regardless of #includes. – rici Aug 12 '20 at 15:05
  • @Eric: So I think @acorn is correct, and you cannot avoid the reservation in 7.31 by making the name `static`. – rici Aug 12 '20 at 15:06
  • @rici: 7.31 says the external names described below are reserved, regardless of headers included by the program. The `isnumber` of `static isnumber…` is not an external name. I could see the file scope reservations of 7.1.3 applying **if** `isnumber` is declared in any header in a future C standard version, but not just because it might appear in a future version. I.e., using `static isnumber…` is portable in C 2018 and earlier (must be accepted and have “normal” behavior by conforming implementations), and we do not know whether it will be portable in future versions. – Eric Postpischil Aug 12 '20 at 15:13
  • @Eric: It doesn't say that the "names described below" are reserved for external linkage. It says that the "external names described below" (i.e. other than the macro names) are reserved, period. So `static isnumber` is not portable in C2018 (or C2011 or C99) because `isnumber` is reserved by 7.31 (or 7.26 in earlier versions). That is, not only might a future standard implement `isnumber` in some (unspecified) header, but an existing implementation (such as Apple's clang) could define `isnumber` in some (unspecified) header. That's my interpretation. You're free to disagree. – rici Aug 12 '20 at 15:50
  • Redeclaring a function as `static` is an error, so I don't think that would help unless you don't include the header. – Acorn Aug 12 '20 at 21:58
  • @acorn: I think the context is precisely the case where the header is not included. If the header is included, the name is reserved. If the header is not included, I believe the name is still reserved (unconditionally) whereas Eric (aiui) believes the name is only reserved for use with external linkage. – rici Aug 12 '20 at 22:06
0

Yes, they can be disabled. But before disabling anything I strongly suggest reading Acorn's answer. Just because you can disable them, it doesn't mean you should. For example, attempting to disable the Apple additions was the wrong solution in my case. But my question was 'Can ...' not 'Should ...'.

They can be disabled by adding #define _POSIX_C_SOURCE or #define _ANSI_SOURCE before #include ctype.h. This will 'disable' the Apple additons to the library, e.g.:

#define _POSIX_C_SOURCE

#include <ctype.h>
#include <stdio.h>
...

If you only want to define this when on maxOS you can first check for the system defined macro __MACH__, (see this question), e.g.:

#ifdef __MACH__
#define _POSIX_C_SOURCE
#endif

#include <ctype.h>
#include <stdio.h>
...
Explanation:

Apple's ctype.h includes _ctype.h where the additional declarations and definitions are guarded by:

#if !defined(_ANSI_SOURCE) && (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE))

Thank you to FelixG who first to pointed me in this direction.

codybartfast
  • 7,323
  • 3
  • 21
  • 25
  • 2
    `_ANSI_SOURCE` is from BSD. Depending on it makes your code even less portable as you are almost certainly using POSIX anyway. In that case, `isnumber()` is ***still*** conflicting with POSIX. – Andrew Henle Aug 12 '20 at 09:57
  • Thanks @AndrewHenle, no I don't think I'll be using POSIX, so _ANSI_SOURCE is probably correct for me, but I'll update the answer to reflect that _POSIX_C_SOURCE may often be the better choice. – codybartfast Aug 12 '20 at 10:17
  • *I don't think I'll be using POSIX* So you're just accessing files via functions such as `fopen()`/`fread()`? You're not doing anything with directories? You're not starting and waiting for child processes? You're not using sockets? You're not using signal functionality beyond the simplistic `signal()` function with a load of `#if ...` to handle its differences on different platforms? You're not using `open()`/`read()`/`write()`/`close()`? You not checking permissions on files? – Andrew Henle Aug 12 '20 at 11:31
  • @AndrewHenle. Correct. I could lose ```fopen()``` if I had to and get by with ```getc()```, ```putc()``` to ```stdin``` and ```stdout```. – codybartfast Aug 12 '20 at 12:31