Determine #defined string length at compile time
Asked Answered
A

5

29

I have a C-program (an Apache module, i.e. the program runs often), which is going to write() a 0-terminated string over a socket, so I need to know its length.

The string is #defined as:

#define POLICY "<?xml version=\"1.0\"?>\n" \
   "<!DOCTYPE cross-domain-policy SYSTEM\n" \
   "\"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\">\n" \
   "<cross-domain-policy>\n" \
   "<allow-access-from domain=\"*\" to-ports=\"8080\"/>\n" \
   "</cross-domain-policy>\0"

Is there please a way, better than using strlen(POLICY)+1 at the runtime (and thus calculating the length again and again)?

A preprocessor directive, which would allow setting POLICY_LENGTH already at compile time?

Aideaidedecamp answered 23/10, 2010 at 9:56 Comment(0)
S
50

Use sizeof(). e.g. sizeof("blah") will evaluate to 5 at compile-time (5, not 4, because the string literal always includes an implicit null-termination character).

Sitting answered 23/10, 2010 at 9:59 Comment(3)
You might want to make sure it's clear why it evaluates to 5 and not 4.Backus
sizeof("blah") is 5. But the problem is that sizeof(blah) where char * blah = "blah" is 4 because it is a pointer size in 32-bit system. It is a problem because we do not need sizeof("concrete string"). We need sizeof(given_pointer). You will have to use strlen(pointer) instead but is sux and the need to add +1 is a minor problem here.Melaniemelanin
@Melaniemelanin indeed sizeof() will give the wrong answer if passed a pointer instead of an array; and if you get in the habit of using it to determine the length of string literals, it is easy to accidentally pass a char* array instead. However, using C11 _Generic we can create a type-safe macro which fails if passed a pointer, see my answer: stackoverflow.com/a/72836532Diocesan
V
7

Use 1+strlen(POLICY) and turn on compiler optimizations. GCC will replace strlen(S) with the length of S at compile time if the value from S is known at compile time.

Vaduz answered 15/1, 2014 at 12:19 Comment(0)
C
6

I have a similar problem when using an outdated compiler (VisualDSP) on an embedded platform which does not yet support C++11 (and so I can't use constexpr).

I don't need to evaluate the string length in the precompiler, but I do need to optimize it into a single assignment.

Just in case someone needs this in the future, here's my extremely hacky solution which should work on even crappy compilers as long as they do proper optimization:

#define STRLENS(a,i)        !a[i] ? i : // repetitive stuff
#define STRLENPADDED(a)     (STRLENS(a,0) STRLENS(a,1) STRLENS(a,2) STRLENS(a,3) STRLENS(a,4) STRLENS(a,5) STRLENS(a,6) STRLENS(a,7) STRLENS(a,8) STRLENS(a,9) -1)
#define STRLEN(a)           STRLENPADDED((a "\0\0\0\0\0\0\0\0\0")) // padding required to prevent 'index out of range' issues.

This STRLEN macro will give you the length of the string literal that you provide it, as long as it's less than 10 characters long. In my case this is enough, but in the OPs case the macro may need to be extended (a lot). Since it is highly repetitive you could easily write a script to create a macro that accepts 1000 characters.

PS: This is just a simple offshoot of the problem I was really trying to fix, which is a statically-computed HASH value for a string so I don't need to use any strings in my embedded system. In case anyone is interested (it would have saved me a day of searching and solving), this will do a FNV hash on a small string literal that can be optimized away into a single assignment:

#ifdef _MSC_BUILD
#define HASH_FNV_OFFSET_BASIS   0x811c9dc5ULL
#define HASH_TYPE               int
#else   // properly define for your own compiler to get rid of overflow warnings
#define HASH_FNV_OFFSET_BASIS   0x811c9dc5UL
#define HASH_TYPE               int
#endif
#define HASH_FNV_PRIME          16777619
#define HASH0(a)                (a[0] ? ((HASH_TYPE)(HASH_FNV_OFFSET_BASIS * HASH_FNV_PRIME)^(HASH_TYPE)a[0]) : HASH_FNV_OFFSET_BASIS)
#define HASH2(a,i,b)            ((b * (a[i] ? HASH_FNV_PRIME : 1))^(HASH_TYPE)(a[i] ? a[i] : 0))
#define HASHPADDED(a)           HASH2(a,9,HASH2(a,8,HASH2(a,7,HASH2(a,6,HASH2(a,5,HASH2(a,4,HASH2(a,3,HASH2(a,2,HASH2(a,1,HASH0(a))))))))))
#define HASH(a)                 HASHPADDED((a "\0\0\0\0\0\0\0\0\0"))
Celebrated answered 11/12, 2017 at 15:32 Comment(1)
Years later, but shout out to those who know the challenge of VisualDSP's deprecation <3Bookstore
C
3

sizeof works at compile time

#define POLICY "<?xml version=\"1.0\"?>\n" \
   "<!DOCTYPE cross-domain-policy SYSTEM\n" \
   "\"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\">\n" \
   "<cross-domain-policy>\n" \
   "<allow-access-from domain=\"*\" to-ports=\"8080\"/>\n" \
   "</cross-domain-policy>\0"

char pol[sizeof POLICY];
strcpy(pol, POLICY); /* safe, with an extra char to boot */

If you need a pre-processor symbol with the size, just count the characters and write the symbol yourself :-)

#define POLICY_LENGTH 78 /* just made that number up! */
Couldst answered 23/10, 2010 at 10:4 Comment(2)
@Oli: I'm guessing pmg meant an expression valid for use in preprocessor conditionals, which sizeof is not..Backus
Having a define for the string and a separate define for its length is a bug risk. If you or someone else changes the string, but forgets to update the length. You can use compile-time asserts to catch these cases.Depository
D
1

Per other answers, sizeof(STRING) gives the length (including the \0 terminator) for string literals. However, it has one downside: if you accidentally pass it a char* pointer expression instead of a string literal, it will return an incorrect value–the pointer size, which will be 4 for 32-bit programs and 8 for 64-bit–as the following program demonstrates:

#include <stdio.h>

#define foo     "foo: we will give the right answer for this string"
char    bar[] = "bar: and give the right answer for this string too";
char   *baz   = "baz: but for this string our answer is quite wrong"; 

#define PRINT_LENGTH(s) printf("LENGTH(%s)=%zu\n", (s), sizeof(s))

int main(int argc, char **argv) {
   PRINT_LENGTH(foo);
   PRINT_LENGTH(bar);
   PRINT_LENGTH(baz);
   return 0;
}

However, if you are using C11 or later, you can use _Generic to write a macro which will refuse to compile if passed something other than a char[] array:

#include <stdio.h>

#define SIZEOF_CHAR_ARRAY(s) (_Generic(&(s), char(*)[sizeof(s)]: sizeof(s)))

#define foo     "foo: we will give the right answer for this string"
char    bar[] = "bar: and give the right answer for this string too";
char   *baz   = "baz: but for this string our answer is quite wrong"; 

#define PRINT_LENGTH(s) printf("LENGTH(%s)=%zu\n", (s), SIZEOF_CHAR_ARRAY(s))

int main(int argc, char **argv) {
    PRINT_LENGTH(foo);
    PRINT_LENGTH(bar);
    // Will fail to compile if you uncomment incorrect next line:
    // PRINT_LENGTH(baz);
    return 0;
}

Note this doesn't only work for string literals – it also works correctly for mutable arrays, provided they are actual char[] arrays of fixed length as a string literal is.

As written, the above SIZEOF_CHAR_ARRAY macro will fail for const expressions (although you'd think string literals ought to be const, for backward compatibility reasons they are not):

const char quux[] = "quux is const";
// Next line will fail to compile:
PRINT_LENGTH(quux);

However, we can improve our SIZEOF_CHAR_ARRAY macro so the above example will also work:

#define SIZEOF_CHAR_ARRAY(s) (_Generic(&(s), \
    char(*)[sizeof(s)]: sizeof(s), \
    const char(*)[sizeof(s)]: sizeof(s) \
))
Diocesan answered 2/7, 2022 at 4:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.