Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to distinguish constant string from char* in C macro

I want to write a macro to write a string, using the compile-time optimisation of knowing the length of a string literal. But I need to detect misuse, using pointers.

Here's what I mean:

void transmit(const char *data, int length);
#define tx_string(x) transmit((x), sizeof(x) -1)
void func(char *badmsg) {
  tx_string("GO");    // ok
  tx_string(badmsg);  // not OK
}

With the second call, the size will be nonsense (sizeof a pointer).

I want to produce a compile-time error if I attempt to use tx_string on anything other than a string literal. This is using gcc; is there some gcc thing I can use to do this?

Edit: I work with data buffers that may contain nulls or not be terminated by nulls. I REALLY want to prevent pointers being used for this, and prevent run-time strlen() use.

Edit 2:

Here's an example which would cause a problem. I'll invent an imaginary protocol where I have to tell a 16-bit microcontroller an address using the command GO followed by an address as 16-bit raw (two 8-bit characters), and I want to go from address 0.

#define GOSEQ "GO\000\000"

void func(void) {
  char *bad = GOSEQ;
  tx_string(GOSEQ); // ok, sends 4 bytes: GO and two zero bytes
  tx_string(bad);   // bad, using runtime strlen sends two characters "GO"
}

I feel sure there must be some kind of gcc builtin check for this. I see the Linux kernel sources using compile-time distinction tricks like this a lot.. but can't lay my hands on a specific one quickly.

So far "Windows programmer"'s idea looks good, but a more meaningful compile-time error would be a bonus.

like image 830
blueshift Avatar asked Nov 10 '11 05:11

blueshift


3 Answers

In general, since you cannot use string concatenation with pointers etc, maybe you can use:

#define STRLIT(x)  x ""

If the argument to STRLIT is not a string literal, you will get a compilation error.

Adapting the general to your specific macro:

#define tx_string(x) transmit((x ""), sizeof(x) - 1)
like image 162
Jonathan Leffler Avatar answered Nov 05 '22 17:11

Jonathan Leffler


You can do this:

#define tx_string(x) transmit(x, strlen(x)) // No need for extra parentheses

GCC will optimize out the strlen call, and I'm sure other compilers will too. Don't waste your time on this stuff.

Test file:

#include <string.h>
void transmit(const char *, size_t);
#define tx_string(x) transmit(x, strlen(x))
void func(void)
{
    tx_string("abcdef");
}

Resulting assembly:

I trimmed noise out of the assembly, this is the important stuff. Notice that it never calls strlen, instead it just uses the number 6:

.LC0:
    .string "abcdef"

func:
    movl    $6, %esi
    movl    $.LC0, %edi
    jmp     transmit
like image 5
Dietrich Epp Avatar answered Nov 05 '22 17:11

Dietrich Epp


#define tx_string(x) ("i came, i concatenated, i discarded " x, transmit((x), sizeof(x) - 1))

Not quite perfect because some joker could call tx_string(+1)

like image 1
Windows programmer Avatar answered Nov 05 '22 15:11

Windows programmer