If you go by the numeric key route, here is one "trick" that might be useful.
Instead of declaring the keys only as preprocessor macros, you declare them as enums instead:
enum {
CONF_URL = 1, /*!< Main configuration URL string */
#define CONF_URL CONF_URL
CONF_FOO, /* !< Some feature named FOO */
#define CONF_FOO CONF_FOO
};
In C, these (CONF_URL and CONF_FOO) are ints. In C++, you can use enum typename: integer-type { ... }; instead, so that you get a new type named typename that is of integer-type, say uint8_t for example.
Instead of maintaining these in that form, you can make a file in your preferred format with three fields per record: name-suffix, integer-value (or a marker for auto-numbering, like CONF_FOO is above), and comment-description, and then use a script to generate a pair of lines for each. If the header file then contains
enum {
#include "generated-enums.inc"
};
with the script output saved to generated-enums.inc, you can include the generation in your Makefile-based build facilities very easily. (Just make generated-enums.inc a prerequisite of the target header file, with the rule running the generator script when necessary. If you ever add proper dependency tracking (make dep) so that all affected files get recompiled when any source file gets modified, Everything Just Works, this way.)
If you want, I can easily construct a small example, but do remember I use Linux. (So state your preferred language for the generator: bash, sh, awk, sed, perl, python. Since the generator is run on the host, it makes sense to use a scripting language instead of C, because when cross-compiling to a different architecture, you may not even have a C toolchain that can generate code to run on the host itself.)