Hacker News new | ask | show | jobs
by kazinator 2760 days ago
> GSG prefers #ifndef/#define idiom over the simpler #pragma once. Yes, #pragma once is non-standard but widely supported.

Google is right here; a purely build-time issue, like avoiding including a header file twice, should be solved without having to resort to nonstandard language features.

The generated code doesn't change in any way, so it is gratuitous.

Save the language extensions for doing something that is inherently nonportable, like gaining access to some platform feature.

1 comments

Meh. C and C++ should just standardize on #pragma once. Everything supports it. It's trivial for new compilers to implement. There's no reason a stupid preprocessor hack should still be required in order to prevent double-inclusion.
Is it trivial to implement?

The detailed semantics of #pragma once is murky. If #pragma once appears in a file, will that preclude an identical copy of the file from being included? Do we go by content, or just some filesystem identifier like device and inode number? Or the absolute path? Will #pragma once preclude that same file from being included again through a symbolic or hard link? Is the answer the same for all compilers?

The semantics of the #ifndef/#define/#endif trick is crystal clear; we can predict what it will do in any given situation. The identity of the file, for the purpose of suppressing duplicates, is tied to a made-up symbol which the program explicitly specifies, and that's that. It may go wrong (e.g. due to clashes on the identifier between unrelated files), but in a way that is understandable.

#pragma once seems like a bad trade-off for the sake of saving two lines of preprocessing.

I agree that it's trivial to invoke a member function called MarkFileIncludeOnce on some object in response to encountering a #pragma once.

I don't think there is any good way to specify how this should behave for all the corner cases; but a given specification isn't hard to implement.

You could say that there's no reason we're still using a stupid preprocessor hack of textual inclusion to import interfaces.

As usual, the devil lies in the detail. If you want to make pragma once robust you need to checksum files, which in the end will be slower than include guards.

If you want to make #pragma once robust, you firstly have to specify the requirements rigorously. The requirements can be specified in such a way that checksumming of files is avoided. We can stipulate that exact (as well as inexact) copies of a file are distinct objects with their own identity under #pragma once, and are not mutually excluded.

#pragma once can be defined in terms of a reference model whereby it is equivalent to a machine-generated sequence:

  #ifndef <ident>
  #define <ident>

  #endif
where the detailed semantics is tied to how the machine generates <ident>.

If <ident> is a digest of the absolute path, then references to the same file via different hard or symbolic links look different and do not mutually exclude.

If <ident> is produced from the volume and object identifier (like inode number) then different links to the same header will mutually exclude.

If <ident> is a content hash, then identical files will mutually exclude (but we need to deal with hash collisions somehow).

A much better solution would be to sidestep this whole thing entirely and just allow any file-scope definition in C++ to be repeated in the same translation unit (with some proviso, like that the multiple definitions have to be identical; and that could be enforced with diagnostics). The multiple inclusions of the same material aren't a problem.

> You could say that there's no reason we're still using a stupid preprocessor hack of textual inclusion to import interfaces.

You could say that. I didn't. Bolting on some sort of module system is just a totally different scope of enhancement than my modest proposal.

> As usual, the devil lies in the detail. If you want to make pragma once robust you need to checksum files, which in the end will be slower than include guards.

That's just not true. It doesn't need to be robust against byzantine source trees and/or build systems to be defined in the C standard or to be useful. The standard can leave the concept of "the same file" implementation-defined, as it does many other concepts.

On Unix system C implementations, it is sufficient to use stat() and compare st_ino and st_dev against previously observed values for a given compilation unit. You do not need to checksum files. You especially do not need to write the specific behavior of checksumming header files into the C standard.

> It doesn't need to be robust against byzantine source trees

A locally developed hack, or even a compiler extension, doesn't have to be; an ISO-standard feature should be well specified.

If something is specified in such a way that it is less robust than the #ifndef trick, then any programmer worth their salt will use the #ifndef trick.

An acceptable pragma-once would look like this:

  #pragma once 9DF9-C3D9-BDF0
Basically it should take an argument string which specifies an ID for the file, intended to be unique.
> ISO-standard feature should be well specified.

That claim doesn't match up with the C standard I've read. I.e., this is an "isolated demand for rigor." Are we looking at the same document? Quite a lot is underspecified or implementation defined to accommodate differences in architectures and systems.

The 2018 C standard doesn't specify whether NULL is a pointer or integer; what a null pointer's representation is; nor the representation of negative (signed) integers. The C standard defines some loose requirements around observable behavior and leaves the specific details to the implementation.

> Basically it should take an argument string which specifies an ID for the file, intended to be unique.

Or the standard could leave it up to the implementation to identify file uniqueness without this additional, incompatible argument. Like I said before, all you have to do for Unix implementations to be as robust as the stupid ifndef trick is to check st_ino and st_dev.

It's important to recognize that implementations and implementation details are distinct from the standard.

Also note that the ifndef hack is non-robust in its own way — false positive exclusions due to accidental identifier conflicts. #pragma once does not have this problem.

The #ifndef trick is highly portable and has clear semantics that is under the program's control. Nobody needs a platform-specific, underspecified alternative that saves two lines of code compared to the robust, portable solution. If I am paying with nonportability, I want "bang for the buck": like faster or smaller code on the target, or detailed control of its hardware or host platform features. I don't need unspecified build machine behavior so much. I am not programming the build machine.