Motherboard Forums


Reply
Thread Tools Display Modes

C preprocessor magic to put a define in a string

 
 
Tim Wescott
Guest
Posts: n/a
 
      05-16-2012, 12:12 AM
Title says it all.

I'd like to be able to pass version numbers in as command-line defines,
then do some magic such that they can appear both in strings and in
numbers.

So, I call something like:

g++ -DMAJOR_VERSION=3 -DMINOR_VERSION=10 version.cpp

and in the code I do one thing in one place:

unsigned int major = MAJOR_VERSION;
unsigned int minor = MINOR_VERSION;

and in another I end up with a string that says

"blah tee blah tee blah version 3.10"

But -- how do I make the blasted string?

At worst, I can make everything strings and use str2long, but I prefer to
do as much during the compilation step as possible.

--
My liberal friends think I'm a conservative kook.
My conservative friends think I'm a liberal kook.
Why am I not happy that they have found common ground?

Tim Wescott, Communications, Control, Circuits & Software
http://www.wescottdesign.com
 
Reply With Quote
 
 
 
 
Walter Banks
Guest
Posts: n/a
 
      05-16-2012, 12:28 AM


Tim Wescott wrote:

> Title says it all.
>
> I'd like to be able to pass version numbers in as command-line defines,
> then do some magic such that they can appear both in strings and in
> numbers.
>
> So, I call something like:
>
> g++ -DMAJOR_VERSION=3 -DMINOR_VERSION=10 version.cpp
>
> and in the code I do one thing in one place:
>
> unsigned int major = MAJOR_VERSION;
> unsigned int minor = MINOR_VERSION;
>
> and in another I end up with a string that says
>
> "blah tee blah tee blah version 3.10"
>
> But -- how do I make the blasted string?


C pre processor can concatenate two strings with ##

#define MyString(maj,minor) blah tee blah tee blah ## major##.##minor

called with

#define MAJOR_VERSION 3
#define MINOR_VERSION 10

Or command line define

MyString(MAJOR_VERSION,MINOR_VERSION)

generates

blah tee blah tee blah 3.10

w..

 
Reply With Quote
 
 
 
 
Les Cargill
Guest
Posts: n/a
 
      05-16-2012, 12:48 AM
Tim Wescott wrote:
> Title says it all.
>
> I'd like to be able to pass version numbers in as command-line defines,
> then do some magic such that they can appear both in strings and in
> numbers.
>
> So, I call something like:
>
> g++ -DMAJOR_VERSION=3 -DMINOR_VERSION=10 version.cpp
>
> and in the code I do one thing in one place:
>
> unsigned int major = MAJOR_VERSION;
> unsigned int minor = MINOR_VERSION;
>
> and in another I end up with a string that says
>
> "blah tee blah tee blah version 3.10"
>
> But -- how do I make the blasted string?
>
> At worst, I can make everything strings and use str2long, but I prefer to
> do as much during the compilation step as possible.
>


Welcome to quoting hell.

Compile line:
gcc -DMAJOR_REV="\"4.3\"" -o eff.exe eff.c

Please note the extra, *sacrificial*, quote marks!

Program:


#include <stdio.h>
#include <string.h>

#ifndef MAJOR_REV
#error OHNO
#endif


const char MAJOR[] = MAJOR_REV;

int main(void)
{

printf("MAJOR=<%s>\n",MAJOR);

return 0;
}


When doing this sort of thing with gcc, the -E option is most helpful.

--
Les Cargill

 
Reply With Quote
 
Tim Wescott
Guest
Posts: n/a
 
      05-16-2012, 01:33 AM
On Tue, 15 May 2012 20:27:17 -0400, Radey Shouman wrote:

> Tim Wescott <(E-Mail Removed)> writes:
>
>> Title says it all.
>>
>> I'd like to be able to pass version numbers in as command-line defines,
>> then do some magic such that they can appear both in strings and in
>> numbers.
>>
>> So, I call something like:
>>
>> g++ -DMAJOR_VERSION=3 -DMINOR_VERSION=10 version.cpp
>>
>> and in the code I do one thing in one place:
>>
>> unsigned int major = MAJOR_VERSION;
>> unsigned int minor = MINOR_VERSION;
>>
>> and in another I end up with a string that says
>>
>> "blah tee blah tee blah version 3.10"
>>
>> But -- how do I make the blasted string?
>>
>> At worst, I can make everything strings and use str2long, but I prefer
>> to do as much during the compilation step as possible.

>
> #include <stdio.h>
>
> #define VERSION_STRING_1(major, minor) "blah tee blah tee blah version "
> #major "." #minor #define VERSION_STRING(major, minor)
> VERSION_STRING_1(major, minor)
>
> int main ()
> {
> fputs(VERSION_STRING(MAJOR_VERSION, MINOR_VERSION), stdout); return 0;
> }


#

Thank you. It's even in my C book, now that I know what to look for.

--
My liberal friends think I'm a conservative kook.
My conservative friends think I'm a liberal kook.
Why am I not happy that they have found common ground?

Tim Wescott, Communications, Control, Circuits & Software
http://www.wescottdesign.com
 
Reply With Quote
 
Andy
Guest
Posts: n/a
 
      05-16-2012, 03:05 AM
On May 15, 8:33*pm, Tim Wescott <(E-Mail Removed)> wrote:
> On Tue, 15 May 2012 20:27:17 -0400, Radey Shouman wrote:
> > Tim Wescott <(E-Mail Removed)> writes:

>
> >> Title says it all.

>
> >> I'd like to be able to pass version numbers in as command-line defines,
> >> then do some magic such that they can appear both in strings and in
> >> numbers.

>
> >> So, I call something like:

>
> >> g++ -DMAJOR_VERSION=3 -DMINOR_VERSION=10 version.cpp

>
> >> and in the code I do one thing in one place:

>
> >> unsigned int major = MAJOR_VERSION;
> >> unsigned int minor = MINOR_VERSION;

>
> >> and in another I end up with a string that says

>
> >> "blah tee blah tee blah version 3.10"

>
> >> But -- how do I make the blasted string?

>
> >> At worst, I can make everything strings and use str2long, but I prefer
> >> to do as much during the compilation step as possible.

>
> > #include <stdio.h>

>
> > #define VERSION_STRING_1(major, minor) "blah tee blah tee blah version "
> > #major "." #minor #define VERSION_STRING(major, minor)
> > VERSION_STRING_1(major, minor)

>
> > int main ()
> > {
> > * fputs(VERSION_STRING(MAJOR_VERSION, MINOR_VERSION), stdout); return0;
> > }

>
> #
>
> Thank you. *It's even in my C book, now that I know what to look for.
>
> --
> My liberal friends think I'm a conservative kook.
> My conservative friends think I'm a liberal kook.
> Why am I not happy that they have found common ground?
>
> Tim Wescott, Communications, Control, Circuits & Softwarehttp://www.wescottdesign.com


you might want an #ifndef to define fallback values or issue a warning
if you forget to set the magic values
 
Reply With Quote
 
Joe Chisolm
Guest
Posts: n/a
 
      05-16-2012, 03:46 AM
On Tue, 15 May 2012 19:12:48 -0500, Tim Wescott wrote:

> Title says it all.
>
> I'd like to be able to pass version numbers in as command-line defines,
> then do some magic such that they can appear both in strings and in
> numbers.
>
> So, I call something like:
>
> g++ -DMAJOR_VERSION=3 -DMINOR_VERSION=10 version.cpp
>
> and in the code I do one thing in one place:
>
> unsigned int major = MAJOR_VERSION;
> unsigned int minor = MINOR_VERSION;
>
> and in another I end up with a string that says
>
> "blah tee blah tee blah version 3.10"
>
> But -- how do I make the blasted string?
>
> At worst, I can make everything strings and use str2long, but I prefer
> to do as much during the compilation step as possible.


#define str(s) #s
#define DtoSTR(x) str(x)

unsigned int major = MAJOR_VERSION;
unsigned int minor = MINOR_VERSION;

char version_string[] = "blah version " DtoSTR(MAJOR_VERSION) "." DtoSTR
(MINOR_VERSION)
#if ALPHA_VERSION > 0
"_a" DtoSTR(ALPHA_VERSION);
#else
#if BETA_VERSION > 0
"_b" DtoSTR(BETA_VERSION)
#endif
;
#endif
char copyright[] = "Copyright " DtoSTR(CPYEAR) " " DtoSTR(CPCOMPANY) ;

CPYEAR and CPCOMPANY are normal -D also with possible quoting if there
is a space in the company name or such. Other than that no
funny quoting.

--
Chisolm
Republic of Texas
 
Reply With Quote
 
Don Y
Guest
Posts: n/a
 
      05-16-2012, 04:34 AM
Hi Tim,

On 5/15/2012 5:12 PM, Tim Wescott wrote:
> Title says it all.
>
> I'd like to be able to pass version numbers in as command-line defines,
> then do some magic such that they can appear both in strings and in
> numbers.
>
> So, I call something like:
> g++ -DMAJOR_VERSION=3 -DMINOR_VERSION=10 version.cpp
> and in the code I do one thing in one place:
>
> unsigned int major = MAJOR_VERSION;
> unsigned int minor = MINOR_VERSION;
>
> and in another I end up with a string that says
> "blah tee blah tee blah version 3.10"
> But -- how do I make the blasted string?
>
> At worst, I can make everything strings and use str2long, but I prefer to
> do as much during the compilation step as possible.


You can glue strings together with the "##" pseudo-operator:

#define NUMBER 4
"This is a string " "that is built from " ## NUMBER ## " pieces."

Note that this doesn't assume NUMBER is actually a "number"!

However, when it comes to versioning, I much prefer letting the
version control system (RCS, SCCS, CVS, SVN, etc.) keep track of
the actual version identifiers for me. Otherwise, you have to
use some other mechanism to "remember" that "*this* binary was
built with *this* snapshot of *these* sources using the command
line XXXXXXXX (which is where you specified minor/major)".

In this case, you can freely use whatever keywords your
VCS supports to tag the sources themselves! (so, when you
check out a particular version of the sources, you get the
sources as they existed at that time -- along with the
associated versioning information).

E.g., under SVN, you might stick the following in your
main.c:

char version = "$Id$";

which SVN will "fill in" (text substitution) for you when you
check out the file so it actually looks more like:

char version = "$Id: main.c 295 2012-05-15 21:18:57 tim $";

(keywords other than "Id" can be used to get smaller portions
of this/different variations)

KNowing this format, you can then extract whatever information
you want from the "version" string at run time:

if (9 == sscanf(version, "$Id: %s %d %d-%d-%d %d:%d:%d %s $";
filename, revision,
year, month, day,
hour, minute, second,
who)
) {
printf("This file, named '%s' was last modified by %s "
"at %d:%d:%d on %d/%d/%d. This is it's %d-th revision.",
filename, who,
hour, minute, second, month, day, year, revision);
}

(Of course, you can do far *less* with this information, too!)

N.B. if you *don't* reference "version" in your code, you usually
have to protect it from lint.

<shrug> I don't like having to put the commands I used for each
"build" under version control in order to keep track of variations
I might have made in invoking the build tools :<

(though makefiles are under the VCS)

YMMV
 
Reply With Quote
 
Stefan Reuther
Guest
Posts: n/a
 
      05-16-2012, 08:23 AM
Walter Banks wrote:
> Tim Wescott wrote:
>>But -- how do I make the blasted string?

>
> C pre processor can concatenate two strings with ##
>
> #define MyString(maj,minor) blah tee blah tee blah ## major##.##minor


No. "##" does not concatenate strings. It concatenates tokens, and
requires both sides of the "##" operator as well as the result to be
valid tokens.

> called with
>
> #define MAJOR_VERSION 3
> #define MINOR_VERSION 10
>
> Or command line define
>
> MyString(MAJOR_VERSION,MINOR_VERSION)
>
> generates
>
> blah tee blah tee blah 3.10


For me, it generates the errors (after fixing the maj->major typo)
foo.c:10:1: pasting "blahMAJOR_VERSION" and "." does not give a valid
preprocessing token
foo.c:10:1: pasting "." and "MINOR_VERSION" does not give a valid
preprocessing token
and the output
blah tee blah tee blahMAJOR_VERSION. 10

The correct operator for stringification is "#". There is no need to
actually concatenate the result strings using the C preprocessor,
because the compiler will do that later just fine. That is,
#define MyString(maj,minor) "blah tee blah tee blah " #maj "." #minor
#define MyString1(maj,minor) MyString(maj,minor)
produces
"blah tee blah tee blah " "3" "." "10"
which the compiler turns into one string.


Stefan

 
Reply With Quote
 
David Brown
Guest
Posts: n/a
 
      05-16-2012, 10:24 AM
On 16/05/2012 05:46, Joe Chisolm wrote:
> On Tue, 15 May 2012 19:12:48 -0500, Tim Wescott wrote:
>
>> Title says it all.
>>
>> I'd like to be able to pass version numbers in as command-line defines,
>> then do some magic such that they can appear both in strings and in
>> numbers.
>>
>> So, I call something like:
>>
>> g++ -DMAJOR_VERSION=3 -DMINOR_VERSION=10 version.cpp
>>
>> and in the code I do one thing in one place:
>>
>> unsigned int major = MAJOR_VERSION;
>> unsigned int minor = MINOR_VERSION;
>>
>> and in another I end up with a string that says
>>
>> "blah tee blah tee blah version 3.10"
>>
>> But -- how do I make the blasted string?
>>
>> At worst, I can make everything strings and use str2long, but I prefer
>> to do as much during the compilation step as possible.

>
> #define str(s) #s
> #define DtoSTR(x) str(x)
>
> unsigned int major = MAJOR_VERSION;
> unsigned int minor = MINOR_VERSION;
>
> char version_string[] = "blah version " DtoSTR(MAJOR_VERSION) "." DtoSTR
> (MINOR_VERSION)
> #if ALPHA_VERSION> 0
> "_a" DtoSTR(ALPHA_VERSION);
> #else
> #if BETA_VERSION> 0
> "_b" DtoSTR(BETA_VERSION)
> #endif
> ;
> #endif
> char copyright[] = "Copyright " DtoSTR(CPYEAR) " " DtoSTR(CPCOMPANY) ;
>
> CPYEAR and CPCOMPANY are normal -D also with possible quoting if there
> is a space in the company name or such. Other than that no
> funny quoting.
>


It is /really/ ugly to split up your initialiser like this with
preprocessor conditionals in the middle. It is hideous style, and it
makes it very easy to make mistakes with things like the semicolons - as
you did in the example.

More controversially, I think it is also bad practice to use "#if" on
macros if they are not defined - use "#ifdef ALPHA_VERSION" of preference.


A better way to write this would be:

#ifdef ALPHA_VERSION
#define ALPHA_VERSION_STRING "_a" DtoSTR(ALPHA_VERSION)
#else
#define ALPHA_VERSION_STRING ""
#endif

#ifdef BETA_VERSION
#define BETA_VERSION_STRING "_b" DtoSTR(BETA_VERSION)
#else
#define BETA_VERSION_STRING ""
#endif

char version_string[] = "blah version " DtoSTR(MAJOR_VERSION) "."
DtoSTR(MINOR_VERSION) ALPHA_VERSION_STRING BETA_VERSION_STRING;


 
Reply With Quote
 
David Brown
Guest
Posts: n/a
 
      05-16-2012, 10:56 AM
On 16/05/2012 06:34, Don Y wrote:
> Hi Tim,
>
> On 5/15/2012 5:12 PM, Tim Wescott wrote:
>> Title says it all.
>>
>> I'd like to be able to pass version numbers in as command-line defines,
>> then do some magic such that they can appear both in strings and in
>> numbers.
>>
>> So, I call something like:
>> g++ -DMAJOR_VERSION=3 -DMINOR_VERSION=10 version.cpp
>> and in the code I do one thing in one place:
>>
>> unsigned int major = MAJOR_VERSION;
>> unsigned int minor = MINOR_VERSION;
>>
>> and in another I end up with a string that says
>> "blah tee blah tee blah version 3.10"
>> But -- how do I make the blasted string?
>>
>> At worst, I can make everything strings and use str2long, but I prefer to
>> do as much during the compilation step as possible.

>
> You can glue strings together with the "##" pseudo-operator:
>
> #define NUMBER 4
> "This is a string " "that is built from " ## NUMBER ## " pieces."
>
> Note that this doesn't assume NUMBER is actually a "number"!
>
> However, when it comes to versioning, I much prefer letting the
> version control system (RCS, SCCS, CVS, SVN, etc.) keep track of
> the actual version identifiers for me. Otherwise, you have to
> use some other mechanism to "remember" that "*this* binary was
> built with *this* snapshot of *these* sources using the command
> line XXXXXXXX (which is where you specified minor/major)".
>
> In this case, you can freely use whatever keywords your
> VCS supports to tag the sources themselves! (so, when you
> check out a particular version of the sources, you get the
> sources as they existed at that time -- along with the
> associated versioning information).
>
> E.g., under SVN, you might stick the following in your
> main.c:
>
> char version = "$Id$";
>
> which SVN will "fill in" (text substitution) for you when you
> check out the file so it actually looks more like:
>
> char version = "$Id: main.c 295 2012-05-15 21:18:57 tim $";
>
> (keywords other than "Id" can be used to get smaller portions
> of this/different variations)
>


No, subversion will /not/ do such keyword substitution unless you
specifically tell it to do so for backwards compatibility with outdated
version control philosophies. The modern style (for at least the last
decade) has been that a source code version control system is there to
track your source code - not to fiddle with it, re-write parts of it, or
modify the code. There are lots of good reasons /not/ to do this.

First, someone other that /you/, the programmer, is modifying your
source files. It's fine to have software write parts of your source
code - but the software should write /its/ files, not /your/ files.

Secondly, it disturbs the integrity of the source code by making changes
at unexpected times. A system where a check out changes the code is
broken - a version control system should give you exactly the files you
asked for, when you ask for them. No more and no less. When I compile
a project, then check it in, then check it out again and re-compile, the
source code had better be 100% identical and give 100% identical binaries.

Thirdly, it doesn't work for binary files or files which might happen to
use the same syntax in the file. So either you are only applying it to
some files, which makes it inconsistent and you have the chance of
forgetting to enable it for important files, or you break your binary files.

Forth, the information contained in the version control system is not
the same as the information you /actually/ want. There are all sorts of
changes that can be made without passing through the version control
system, but which might need tracked by version numbers or similar
information. Conversely, you will often want to check things in without
changing version numbers or revision numbers.

There are also better ways to achieve the same effects if you really
want them. If you need information about the versions or logs tracked
by the version control system, ask the version control system at the
time that you need them (your binary output should not need these
details, only your developers). If you need some general information to
be included in the build, then add makefile commands to get that
information from the repository at build time.

It is with good reason that many modern version control systems (such as
git and mercurial) do not support keyword expansion, or discourage them
(as subversion does).

<http://mercurial.selenic.com/wiki/KeywordPlan>
<http://wordaligned.org/articles/keyword-substitution-just-say-no>




> KNowing this format, you can then extract whatever information
> you want from the "version" string at run time:
>
> if (9 == sscanf(version, "$Id: %s %d %d-%d-%d %d:%d:%d %s $";
> filename, revision,
> year, month, day,
> hour, minute, second,
> who)
> ) {
> printf("This file, named '%s' was last modified by %s "
> "at %d:%d:%d on %d/%d/%d. This is it's %d-th revision.",
> filename, who,
> hour, minute, second, month, day, year, revision);
> }
>
> (Of course, you can do far *less* with this information, too!)
>
> N.B. if you *don't* reference "version" in your code, you usually
> have to protect it from lint.
>
> <shrug> I don't like having to put the commands I used for each
> "build" under version control in order to keep track of variations
> I might have made in invoking the build tools :<
>
> (though makefiles are under the VCS)
>
> YMMV


 
Reply With Quote
 
 
 
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Preprocessor conditionals *within* an expression D Yuniskis Embedded 10 03-28-2010 05:30 PM
Benchmarks: STL's string vs. C string Jyrki Saarinen Embedded 58 12-04-2007 05:49 PM
[ANN] Advanced macro processor (preprocessor) Unimal 2.0a release Ark Embedded 0 07-25-2006 05:46 AM
[ANN] Unimal 2.0, a language-independent preprocessor Ark Embedded 21 04-23-2006 03:54 PM
Please define slipstreamed Wildbill Asus 4 04-26-2004 03:45 PM


All times are GMT. The time now is 02:37 AM.


Welcome!
Welcome to Motherboard Point
 

Advertisment