-1

Currently for a header file named test_header.h I use -

#ifndef TEST_HEADER_H
#define TEST_HEADER_H   
/* code */
#endif /* TEST_HEADER_H */

What I want is a header guard which doesn't use the file name directly. Something like (i.e. wishful crude hypothetical solution) -

#if __FILE__ not in INCLUDE_LIST
#APPEND(INCLUDE_LIST, __FILE__)
/* code */
#endif
Brian Tompsett - 汤莱恩
  • 5,195
  • 62
  • 50
  • 120
work.bin
  • 1,048
  • 5
  • 25
  • 3
    Strictly speaking, you don't _need_ to change header guard after renaming - not unless you have another file that takes over the old name. –  Dec 28 '16 at 16:20
  • I know, but this way I can be worry-less (sic). – work.bin Dec 28 '16 at 16:29
  • 2
    You can use an identifier with a prefix plus a UUID and be even more worry-less. – Michael Foukarakis Dec 28 '16 at 16:32
  • @MichaelFoukarakis - I didn't get it; could you explain it using the _test_header.h_ example. – work.bin Dec 28 '16 at 16:34
  • 3
    `#pragma once` is supported on many compilers... – Eugene Sh. Dec 28 '16 at 16:56
  • Currently my team follows a strict "no pragma" coding standard (because it reduces portability). _(Maybe it is time to relook the coding standards.)_ – work.bin Dec 28 '16 at 17:01
  • 1
    There are `#pragma once` to include guard converters and vice versa. [Qt Creator recently used one to convert all their headers to use `#pragma once`](https://github.com/qtproject/qt-creator/commit/39a38d5679084b515276285c044d8a27e671adb1) – milleniumbug Dec 28 '16 at 17:23
  • 2
    @work.bin, there is a handful of pragmas that are in fact defined by standard C -- portability is not an issue for those particular pragmas. But `#pragma once` is not among those, and I'm inclined to agree with the idea of avoiding implementation defined behavior, such as use of non-standard pragmas produces. – John Bollinger Dec 28 '16 at 17:24
  • 1
    Create a UUID or GUID and use that as the header guard (or a hash of some sort — MD5, SHA1, SHA2, SHA3, …). The only trick is dealing with the possibility of a leading digit; that's easily worked around (I used `H_` as a prefix). – Jonathan Leffler Dec 29 '16 at 00:01
  • @JonathanLeffler - So the header guard macro you used was "H_<40 SHA1 chars>", i.e. did you use all 40 characters of the hash (assuming it was SHA1)? – work.bin Dec 29 '16 at 01:59
  • @work.bin : near enough, yes. As long as the content you're hashing is unique it works fine. – Jonathan Leffler Dec 29 '16 at 02:21
  • @JonathanLeffler - Could you please add this as an answer? – work.bin Dec 29 '16 at 02:41

3 Answers3

4

You can name the header guards whatever you like.

  • If your header is related to a certain topic, name it after that i.e. LIST_OPERATIONS
  • Omit the guards and write a script that inserts them based on the current file name. Run this script as part of your build process before compiling. (Remember to either create a modified copy or remove the includes after the build, otherwise you end up with a lot of include guards in your header)
  • Depending on the compilers that should be used for your project, they might support the #pragma once approach.
fer-rum
  • 201
  • 1
  • 5
  • Your script can also delete old include guards if they have distinctive format, or do nothing if the right guards are already present –  Dec 28 '16 at 16:31
3

#pragma once

It's very portable, being well-supported by all major compilers, and by 13 out of 14 compilers (according to Wikipedia).

Also check out #pragma once vs include guards?.

Community
  • 1
  • 1
emlai
  • 37,861
  • 9
  • 87
  • 140
  • 2
    But programs that use `#pragma once` nevertheless are not strictly conforming. If your objective is to write portable code then you will not use it, the proportion of major compilers supporting it notwithstanding. – John Bollinger Dec 28 '16 at 17:17
2

As others have noted, what you use as a header guard isn't intrinsically important; it just needs to be unique across the set of headers that might ever be co-included.

You can create a UUID or GUID and use that as the header guard (or a hash of some sort — MD5, SHA1, SHA2, SHA3, …). The only trick is dealing with the possibility of a leading digit; that's easily worked around (I used H_ as a prefix).

Mostly though, I use a name based on the file name, and don't usually rename headers often enough that it is a problem.

Here's a script called hdrguard that I use for generating the header guard lines for a given header file:

#!/bin/sh
#
# @(#)$Id: hdrguard.sh,v 1.8 2016/05/09 18:41:57 jleffler Exp $
#
# Generate #ifndef sequence to guard header against multiple inclusion

arg0=$(basename $0 .sh)

usestr="Usage: $arg0 [-bdfhimV] header.h [...]"

usage()
{
    echo "$usestr" 1>&2
    exit 1
}

help()
{
    echo "$usestr"
    echo
    echo "  -b  Use base name of file for guard"
    echo "  -d  Use _DOT_H after name (instead of _H)"
    echo "  -f  Use specified path name of file for guard (default)"
    echo "  -h  Print this help message and exit"
    echo "  -i  Omit _INCLUDED after name"
    echo "  -m  Generate MD5 hash value as header guard"
    echo "  -V  Print version information and exit"
    exit 0
}

opt_incl=yes
opt_base=no
opt_dot=no
opt_md5=no
while getopts bdfhimV opt
do
    case "$opt" in
    (b) opt_base=yes;;
    (d) opt_dot=yes;;
    (f) opt_base=no;;
    (h) help;;
    (i) opt_incl=no;;
    (m) opt_md5=yes;;
    (V) echo "$arg0: HDRGUARD Version "'$Revision: 1.8 $ ($Date: 2016/05/09 18:41:57 $)' | rcsmunger; exit 0;;
    (*) usage;;
    esac
done

shift $(($OPTIND - 1))

[ $# -eq 0 ] && usage

for i in "$@"
do
    if [ $opt_base = yes ]
    then i=$(basename $i)
    fi
    if [ $opt_dot = yes ]
    then i=$(echo "$i" | sed 's/\.h$/_dot_h/')
    fi
    i=$(echo $i | tr 'a-z' 'A-Z' | tr -s '/+.-' '____' | sed 's/^_//')
    if [ $opt_incl = yes ]
    then
        case "$i" in
        (*_INCLUDED)
            : OK;;
        (*)
            i="${i}_INCLUDED";;
        esac
    fi
    if [ $opt_md5 = yes ]
    then
        tmp=$(mktemp ./hdrgrd.XXXXXXXX)
        trap "rm -f $tmp; exit 1" 0 1 2 3 13 15 
        echo "$i.$(isodate compact)" > "$tmp"
        i=$(md5 "$tmp" | sed 'y/abcdef/ABCDEF/; s/\([^ ]*\) .*/H_\1/')
        rm -f "$tmp"
        trap 0 1 2 3 13 15
    fi
    echo
    echo "#ifndef $i"
    echo "#define $i"
    echo
    echo "#endif /* $i */"
    echo
done

It doesn't have code for SHA1, SHA2 or SHA3 — it optionally uses MD5 (in the form of a command md5). It would not be very hard to add support for alternative hashing algorithms. It doesn't require the file to exist.

Example uses:

$ hdrguard header.h

#ifndef HEADER_H_INCLUDED
#define HEADER_H_INCLUDED

#endif /* HEADER_H_INCLUDED */

$ hdrguard -m header.h

#ifndef H_6DC5070597F88701EB6D2CCAACC73383
#define H_6DC5070597F88701EB6D2CCAACC73383

#endif /* H_6DC5070597F88701EB6D2CCAACC73383 */

$

I frequently use it from within vim, typing a command such as !!hdrguard % while the cursor is on an empty line to generate a header guard suitable for the header I'm editing. That's why it generates the blank lines top and bottom, too.

The command uses scripts isodate and rcsmunger. With the argument compact, the isodate command is equivalent to:

date +'%Y%m%d.%H%M%S'

The complete command supports a number of alternative formats and is more succinct than having to type the date command out everywhere. You're entirely at liberty to forego the use of a separate script and to just embed the expansion shown into hdrguard. Indeed, you could use just date and it would be OK; it is just seed material for the hashing operation to make the data being hashed unique.

$ isodate compact
20161228.185232
$

The rcsmunger command just converts RCS ID strings into a format I prefer for reporting version information:

#!/usr/bin/env perl -p
#
# @(#)$Id: rcsmunger.pl,v 1.9 2015/11/02 23:54:32 jleffler Exp $
#
# Remove the keywords around the values of RCS keywords

use strict;
use warnings;

# Beware of RCS hacking at RCS keywords!
# Convert date field to ISO 8601 (ISO 9075) notation
s%\$(Date:) (\d\d\d\d)/(\d\d)/(\d\d) (\d\d:\d\d:\d\d) \$%\$$1 $2-$3-$4 $5 \$%go;
# Remove keywords
s/\$([A-Z][a-z]+|RCSfile): ([^\$]+) \$/$2/go;

For example:

$ hdrguard -V
hdrguard: HDRGUARD Version 1.8 (2016-05-09 18:41:57)
$

You can regard the printing of version information as old-school version control; it has to be done differently if you use a DVCS such as git, which is one reason I've not done a wholesale migration to git for my personal software collection.

Jonathan Leffler
  • 666,971
  • 126
  • 813
  • 1,185