Why isn't C/C++'s "#pragma once" an ISO standard?
Asked Answered
C

9

61

I am currently working on a big project and maintaining all those include guards makes me crazy! Writing it by hand is frustrating waste of time. Although many editors can generate include guards this doesn't help much:

  1. Editor generates guard symbol based on a filename. The problem occurs when you have headers with the same filename in different directories. Both of them will get the same include guard. Including directory structure into the guard symbol would require some fancy approach from the editor, since slashes and backslashes in the macro are not the best thing.

  2. When I have to rename a file I should rename all the include guards as well (in the ifndef, define and ideally endif's comment). Annoying.

  3. Preprocessor is flooded with tons of symbols without a clue what they mean.

  4. Nevertheless definition is included once, compiler still opens header every time it meets header inclusion.

  5. Include guards don't fit into namespaces nor templates. In fact they are subverting namespaces!

  6. You have a chance that your guard symbol won't be unique.

Maybe they were acceptable solution in times when programs contained less than 1000 headers in single directory. But nowadays? It is ancient, it has nothing to do with modern coding habits. What bothers me the most is that this issues could be almost compeletly solved by #pragma once directive. Why is it not a standard?

Clientage answered 8/11, 2009 at 8:52 Comment(9)
I fear for any project that has more than 1000 headers in a single directory.Analogical
include guards aren't a big deal. just make them random long strings and unless your source of randomness is abysmal you won't run into any problems.Macadam
having same file names in different directories is a bad practice anyway, especially if they differ in contents.Forelock
Then why you have directories at all? Just give every file different name and directories are useless ;) Isn't this a proove that c++ inclusion system is ancient? It can not utilize directories in efficient way. Giving the same name for files in different locations is natural and a common practice in other languages. In Java for example you have plenty of classes with the same names, thus in the same file names since Java uses one class per file convention, but in different namespaces.Clientage
It is very confusing (and time consuming) for a new programmer to look at a big project which contains files with the same name but have different functionality. Since you mention 1,000 headers makes its even less straight forward.Forelock
No it is not. If you have for example a plugin system it is very likely that some filenames will be repeated. What's confusing? You have directory and you know on which part of application you are working.Clientage
With a GUID, you don't need to rename the guard when you move the file.Electrodynamometer
While they are at it, why don't the ISO standards include a pony? And I was promised flying cars!Schreck
I too wish that the C++ standards committee would standardize either #pragma once or another way to do the same thing. It is true that the filesystem is not under the control of the compiler, and that this can cause problems; but is this sufficient reason to decline to standardize a language/preprocessor feature so many programmers evidently find useful? Many useful language features can be abused, not only #pragma once. Abuse of other useful language features is managed by compiler warnings and good programming practice. Is #pragma once not likewise a feature of this kind?Teenateenage
G
55

A directive like #pragma once is not trivial to define in a fully portable way that has clear an unambiguous benefits. Some of the concepts for which it raises questions are not well defined on all systems that support C, and defining it in a simple way might provide no benefit over conventional include guards.

When the compile encounters #pragma once, how should it identify this file so that it doesn't include its contents again?

The obvious answer is the unique location of the file on the system. This is fine if the system has unique locations for all files but many systems provide links (symlinks and hardlinks) that mean that a 'file' doesn't have a unique location. Should the file be re-included just because it was found via a different name? Probably not.

But now there is a problem, how is it possible to define the behaviour of #pragma once in a way that has an exact meaning on all platforms - even those that don't even have directories, let alone symlinks - and still get the desirable behaviour on systems that do have them?

You could say that a files identity is determined by its contents, so if an included file has a #pragma once and a file is included that has exactly the same contents, then the second and subsequent #includes shall have no effect.

This is easy to define and has well defined semantics. It also has good properties such that if a project is moved from a system that supports and uses filesystem links to one that doesn't, it still behaves the same.

On the downside, every time an include file is encountered containing a #pragma once its contents must be checked against every other file using #pragma once that has already been included so far. This implies a performance hit similar to using #include guards in any case and adds a not insignificant burden to compiler writers. Obviously, the results of this could be cached, but the same is true for conventional include guards.

Conventional include guards force the programmer to choose a macro that is the unique identifier for an include file, but at least the behaviour is well-defined and simple to implement.

Given the potential pitfalls and costs, and the fact the conventional include guards do work, it is not surprising to me that the standards committee didn't feel the need to standardize #pragma once.

Gatekeeper answered 8/11, 2009 at 12:7 Comment(21)
I'm upvoting this for the effort but want to present the other side too. if they are touting portability and finally including constructs that are widely used for over 20 years.. and most importantly, if they are on a payroll, and if standards are supposed to be obeyed, it naturally would piss me off because it is a cost to everyone else but vendors to support non-standard pragmas and by implication, even allow them to exist. that would be a job bad done in my opinion.Palanquin
and sure, while policing is hard to enforce TM Ltd, someone at the committee wonderland should have picked up the idea that inclusion and linking mechanism is really outdaded, error-prone and requires urgent and backward-compatible modernisation to migrate to. they had roughly 15 years of good examples out there and nothing comes out on it yet again..Palanquin
Furthermore, if I remember correctly, #pragma is a preprocessor directive to allow for vendor-specific preprocessor commands, so it's kind of an oxymoron to standardize a pragma.Flagpole
"The obvious answer is the unique location of the file on the system. This is fine if the system has unique locations for all files but many systems provide links (symlinks and hardlinks) that mean that a 'file' doesn't have a unique location. Should the file be re-included just because it was found via a different name? Probably not.".................................................................. Just follow links, they point to explicit file name! Really, this shouldn't be a tremendous task for the compiler building team.Clientage
Compiler interacts with the file system on quite a low level. It must consider different directory separators in different OSes, usually it calls sys functions to determine modification date of source files, and when you include file this also isn't operation with well defined semantics. One one OS file name may have maximaly 8 characters, on other "B" letter may be unallowed character etc. Paradoxically Unix born C can't handle hard/sym -links, which are a POSIX standard.Clientage
Hard link also should point to explicit file name. If for some reason it doesn't then this is user/OS problem and not the compiler problem.Clientage
Going back to the original question, it should be obvious that we are getting system specific so getting a wording that is acceptable for all systems where C is implemented is going to be hard. Compilers don't interact with the filesystem at a fundamentally low level, they are just clients of the service like any other application.Gatekeeper
On a typical Unix filesystem, you can tell from a filename whether there are other hardlinks that link to a given file, there is usually no reverse mapping to tell you what other filenames also link to this file, indeed a link might be just an open file handle with no name. Even if there is another name that links to a file there is no 'primary' name, so either name is just as good... unless someone unlinks one of the names because it was only a temporary filename.Gatekeeper
It seems to me a bit nit-picking. I doubt anyone have ever need hard links functionality during development. But fine, what about defining that pragma once just can not be used within hard links? In this case compiler should trigger an error (but it is of course not obliged).Clientage
And BTW what if programmer includes files from /proc, /dev or other pseudofile system? Or if he includes a pipe or a socket? Of course results of such operations are compeletely undefined, yet you are allowed to use #include.Clientage
Assuming that the path used to resolve an #include can't change over the course of a compilation, couldn't #pragma once just be defined to mean "skip processing any additional #include directives followed by the same filename token", leaving the normal guards to handle things when the same file ends up being specified in different ways? We're going to need those for compatibility with older compilers anyway...Eshelman
I doubt that the standard even specifies that a name referenced by #include is a file on a filesystem ! The include guard is the only way you have to distinguish between different headers.Aggravate
I do not buy it. It is not a problem to implement on each system in a logical way (e.g. linux has inodes, on windows you can get similar information for a file), and from the point of view of the standard it is not a problem to standardize it with some extremely vague implementation-defined way, just as the meaning of #include is also implementation defined.Lueluebke
You know what? File contents don't actually suffice: A files preprocessing can depend on its path and name, for example when including additional files.Mileage
@AlexandreC.: A normative note in §17.6.1.3 reads "A header is not necessarily a source file, nor are the sequences delimited by < and > in header names necessarily valid source file names" ybungalobill: msdn.microsoft.com/en-us/library/aa364952(VS.85).aspx says the FileIndex of the FILE_INFORMATION is unique for each actual file.Floyd
@ybungalobill: msdn.microsoft.com/en-us/library/aa363788(v=vs.85).aspx says the identifier (low and high parts) and the volume serial number uniquely identify a file on a single computer. However, the IDs can change over time, so File index identifiers are only valid so long as open file handles are maintained. That means on Windows the compiler would have to keep an open handle to every header included with a linkcount > 1.Floyd
@MooingDuck: I don't need a compiler protecting itself from crazy race conditions when a file is deleted and replaced with another one in the timespan of processing one translation unit. Even with include guards compilers may open the file once or twice, thus giving different results. It is outside the scope of the standard and the realm of sanity. Besides, using a simple syntactic path normalization and comparison would be enough for me, and for 99% of the users. If sb insists to refer to the same file through different paths within the same translation unit, she can revert to include guards.Lueluebke
@ybungalobill: According to the comments, such renumbering is seen when the OS has to go over the network to reach headers. (I suspect it also depends on the filesystem) Deletion/replacement is not part of this case.Floyd
Why doesn't it just store the guarded file's hash, and check the hash of each included file?Sabbath
@Sabbath perhaps because the contents of said file can change between inclusions based upon macros expanded within itMonopolize
That makes sense, but I was assuming the old preprocessor stuff would be removed, except maybe includes (which should still be replaced with #pragma include/import header.h)Sabbath
S
19

Include guards are definitely an annoyance, and C should have originally been designed such that headers would be included once by default - requiring some special option to include a header multiple times.

However, it wasn't ,and you're mostly stuck with having to use include guards. That said, #pragma once is pretty widely supported so you might be able to get away with using it.

Personally, I solve your 1st problem (similarly named include files) by adding a GUID to the include guard. It's ugly, and most people hate it (so I'm often forced to not use it at work), but your experience shows that the idea has some value - even if it is hideously ugly (but then again the whole include guard thing is kind of a hack - why not go whole hog?):

#ifndef C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546
#define C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546

// blah blah blah...

#endif

I've heard that compilers don't actually reopen header files that have include guards (they've learned to recognize the idiom). I'm not sure if that's true (or to what extent it's true); I've never measured it. I also don't worry about it, but my projects aren't so huge that it's a problem.

My GUID hack pretty much solves items 1, 5, and 6. I just live with items 2, 3, and 4. Actually, for item 2 you could live without renaming the include guard macro when you rename the file as the GUID will ensure it remains unique. In fact, there's no reason to incorporate the filename at all with the GUID. But I do - tradition, I suppose.

Siobhan answered 8/11, 2009 at 9:5 Comment(4)
I agree that this is the best solution, unfortunately I'm using Eclipse which is a great tool but has terrible suport for include guards :/Clientage
I don't agree that this solved namespaces issue. As one may guess namespaces were invented to avoid names collision, and set natural names free again. This is achieved by wrapping all potentialy colliding code into namespace brackets. And of course include guards are exceptional to this rule. So it ends up in some kind of missconception. While structures are going to have more human names, include guards are going into exactly opposite direction. Resolving their name conflicts is left to IDE/scripts/whatever. This isn't pretty philosophy in my opinion.Clientage
I agree that it's not pretty, but it it wasn't meant to be an ideal solution, but a pragmatic one. It does pollute the global namespace, but does so with names that are designed to be universally unique, and the names are used in exactly one place (on 2 lines, but right next to each other). Actually, it's overkill for most people (most projects get by with much simpler guard names). Personally, I see little downside to using them, other than the ugliness - but I don't have to interact with them much, so it's not a bad tradeoff. It would have been better if C didn't need guards, but it does.Siobhan
@MichaelBurr: I would say #pragma once is the "pragmatic" solution.Mason
C
14
  1. How often do you have to add an include file to this project? Is it really so hard to add DIRNAME_FILENAME to the guard? And there is always GUIDs.
  2. Do you really rename files that often? Ever? Also, putting the GUARD in the #endif is just as annoying as any other useless comment.
  3. I doubt your 1000 header files guard defines are even a small percentage of the number of defines generated by your system libraries (especially on Windows).
  4. I think MSC 10 for DOS (20+ years ago) kept track of what headers were included and if they contained guards would skip them if included again. This is old tech.
  5. namespaces and templates should not span headers. Dear me, don't tell me you do this:

    template <typename foo>
    class bar {
    #include "bar_impl.h"
    };
    
  6. You said that already.

Commerce answered 8/11, 2009 at 9:22 Comment(8)
If the headers are as badly laced with conditionals as the ones I work with, having a comment after #endif can be valuable for seeing more easily which condition you are seeing the end of (especially when the start of the conditional is several hundred lines up the file).Electrodynamometer
I can think of use cases like those above (look at boost source code to see namespaced includes in action) but then there won't be any include guards at play, probably.Brigadier
If it's that bad a problem, move the #define to just before the #endif. Then you don't need the comment.Commerce
Another way to avoid having to edit the comment at the guard's #endif when you change the macro name is to use a generic comment: #endif /* close include guard */ It's not going to be very confusing, since there's only ever one include guard section and the endif for an include guard is pretty much always the last line in the header anyway (so a comment is largely redundant anyway, but I can understand wanting one).Siobhan
And it's probably best to name the sort of unguarded header files @Brigadier mentions differently from normal headers, too...Eshelman
Stack overflow needs a cannot_unsee tag for things like this.Trocar
Your template example seems silly because you could move the three extra lines into bar_impl.h anyway and then just have #include "bar_impl.h". The only reason I could see to do it the way you have is if, for some weird reason, the person writing the template had to define template bodies with exactly the same content but different names, which I'm sure has been an issue for somebody somewhere at some point in time.Catamnesis
@JAB, my template example is not "silly" it's frightening, which is why I included it as an example of something NOT to do.Commerce
S
10

As was already noted, C++ Standard should account for different development platforms some of which may have limitations making #pragma once support impossible to implement.

On the other hand, support for threads was not added for the similar reason previously, but newer C++ Standard includes threads nevertheless. And in the latter case we may cross-compile for a very limited platform, but development is done on a full-fledged platform. Since GCC supports this extension, I think, the real answer to your question is that there is no interested party in pushing this feature into C++ Standard.

From practical standpoint, include guards caused our team more trouble than the incompliance of #pragma once directive. For instance, GUID in include guards do not help in case if file is duplicated and later both copies are included. When using only #pragma once we get duplicate definition error and can spend time unifying source code. But in case of include guards the problem may require run-time testing to catch, e.g. this happens if copies differ in default arguments for function parameters.

I avoid using include guards. If I have to port my code to a compiler without #pragma once support, I will write a script which will add include guards to all header files.

Sylvanite answered 8/8, 2011 at 7:52 Comment(0)
F
4

The problem occurs when you have headers with the same filename in different directories.

So you have two headers which are both called ice_cream_maker.h in your project, both of which have a class called ice_cream_maker defined in them which performs the same function? Or are you calling every class in your system foo?

Nevertheless definition is included once, compiler still opens header every time it meets header inclusion.

Edit the code so you don't include headers multiple times.

For dependent headers (rather than the main header for a library), I often use header guards which are like this:

#ifdef FOO_BAR_BAZ_H
#error foo_bar_baz.h multiply included
#else
#define FOO_BAR_BAZ_H

// header body

#endif
Fremont answered 8/11, 2009 at 9:55 Comment(1)
Yeah I know, preprocessor is powerful. However this method brings even more passive code. Why not just #pragma once and you have all this in a single line, which may be blindly pasted even by very primitive editor. In modularity aspects C++ is in early middle ages. Nor Java, nor PHP, nor Perl, nor Python, nor Smalltalk, nor Rebol, nor probably any modern language do inclusions in the way C/C++ does. Even Turbo Pascal got nicer system of units.Clientage
P
3

IIRC, #pragma anything is not part of the language. And it shows up in practice, a lot.

(edit:completely agree that the inclusion and linking system should have been more of a focus for the new standard as it is one of the most obvious weaknesses, in 'le this day and age' )

Palanquin answered 8/11, 2009 at 10:23 Comment(5)
#pragma STDC FP_CONTRACT ON ??Gatekeeper
Don't know Charles, as I can't look it up easily right now.. I'd say it is a compiler directive, rather than language thing but could be way wrong here..Palanquin
I was giving an example of a #pragma something that is defined in the language. ISO/IEC 9899:1999 6.10.6/2 is where the standard C #pragmas are listed (if you wanted to look them up).Gatekeeper
of course you could be right.. perhaps others can chime in as well. anyway, it seems pretty chaotic with: "1996 Any such pragma that is not recognized by the implementation is ignored " "1995 The behavior might cause translation to fail or cause the translator or the resulting program to behave in a non-conforming manner. " "2001 an implementation is permitted to behave as if it were the standard pragma, but is not required to."Palanquin
perhaps those are special, perhaps not. But reading the above just doesn't give me confidence to say it is a not compiler/implementation-dependent interpretation, thus possibly disqualifying it from being defined in the language. all of theoretical value only of course, as the practice has obviously taken on the opposite direction with each vendor shooting ever-increasing pragma-related warnings at each other.Palanquin
B
2

You can probably avoid name collisions without resorting to random strings by setting the include guard to contain the name of the class and namespace that's in the file.

Besides, #pragma once is supported by both MS compilers and GCC for quite some time now so why does it bother you that it's not on the ISO standard?

Beacham answered 8/11, 2009 at 9:10 Comment(2)
I for one am actively using a compiler that doesn't support #pragma once. The OP may need to support such things.Siobhan
Because it is not an ISO standard and so far it is not portable. Moreover GCC had been marking it as obsolete feature for quite a long time. They still don't recommend to use it...Clientage
H
2

A pragmatic solution:

1) choose some consistent guard-naming policy (e.g. path relative to project root + file name, or whatever else you choose). Include possible exceptions for third-party code.

2) write a program (a simple python script would do) to walk recursively the source tree and verify that the guards all conformal to the policy. And whenever the guards are wrong, output a diff (or sed script, or whatever else) that the user can easily apply to fix. Or just ask for a confirmation and make changes from the same program.

3) make everyone on the project use it (say, before submitting to source control).

Havre answered 8/11, 2009 at 10:44 Comment(0)
W
1

I think right way to allow to do multiple include with special pragma only and disallow to multiple include by default for example:

#pragma allow_multiple_include_this_file

So since you asked why. Did you send your proposal to standart developers? :) I'm not send too. Can one be a reason?

Whereunto answered 13/1, 2012 at 4:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.