1

I would like some ideas for a macro to convert a preprocessor defined string to a pascal type string and then be able to use the macro to initialize const char arrays and the like.

Something like this would be great:

#define P_STRING_CONV(str) ...???...

const char *string = P_STRING_CONV("some string");

struct
{
    char str[30];
    ...
}some_struct = {.str = P_STRING_CONV("some_other_string")};

I already tried something like this:

#define DEFINE_PASCAL_STRING(var, str, strlen) struct {uint8_t len; char content[strlen-1];} (var) = {sizeof(str)-1, (str)}

(The strlen parameter could be removed, but I need a defined size.)

That works fine, but cannot be used to initialize elements in a struct. And for const char arrays I need to cast it to some other variable.

Any great ideas?

Tajen
  • 43
  • 4
  • 4
    1. Why are you trying to do this? I think that I don't really understand what is it that you actually want to achieve. – Iharob Al Asimi Aug 30 '17 at 13:33
  • 1
    The major problem is that you seem to think Pascal-like string would be compatible with C strings (pointers to `char`, or arrays of `char`), which it can't really be naturally. What is the problem using Pascal-like string would solve? Why do you want to use them? – Some programmer dude Aug 30 '17 at 13:33
  • 2
    At the very least, the strings should keep a NUL terminator, so reducing their max length to 254 chars. At least then you could use them as const arguments by passing [address+1]. – Martin James Aug 30 '17 at 13:41
  • I need to communicate with a system that uses Pascal like strings, that is the only reason for using it. It is also just for making the code easier to understand. Right now a have to have a function to initialize some variables and for const I need something like `const char string[30] = "\004test";` – Tajen Aug 30 '17 at 14:17
  • 1
    Hmm.. I think I would have gone for a 'PasStr' type and 'CtoPasStr()' and PasToCstr() style 'conversion' functions, even if that requires malloc/free-style string memory management to avoid 1-byte overruns and the like. – Martin James Aug 30 '17 at 15:07
  • BTW, clang does this with the `-fpascal-strings` compiler flag and a special `"\p"` prefix. Example: `"\ptest"` will prefix the string with 0x4. – drudru Aug 30 '23 at 21:04

2 Answers2

2

to convert a string to a pascal string type

To convert a string literal, _Generic and compound literal will get close to OP objective.

For a better solution, more details and example use cases would help illustrate OP's goal.

#define P_STRING_CONV(X) _Generic((X)+0, \
  char *: &((struct {char len; char s[sizeof(X)-1]; }){ (char)(sizeof(X)-1), (X) }).len \
  )

void dump(const char *s) {
  unsigned length = (unsigned char) *s++;
  printf("L:%u \"", length);
  while (length--) {
    printf("%c", *s++);
  }
  printf("\"\n");
}

int main(void) {
  dump(P_STRING_CONV(""));
  dump(P_STRING_CONV("A"));
  dump(P_STRING_CONV("AB"));
  dump(P_STRING_CONV("ABC"));
  return 0;
}

Output

L:0 ""
L:1 "A"
L:2 "AB"
L:3 "ABC"

@Jonathan Leffler recommended that the created pascal-like string also contain a terminating null character. To do so with above code, simple change sizeof(X)-1 into sizeof(X). Then by accessing the pascal_like_string + 1, code has a pointer to a valid C string.


(X)+0 converts an array type to a pointer

sizeof(X)-!!sizeof(X) produces a size of the string literal, not counting its \0. At least 1.

struct {char len; char s[sizeof(X)-!!sizeof(X)]; } Is a right-sized pascal-like structure.

(struct {char len; char s[sizeof(X)-!!sizeof(X)]; }){ (char)(sizeof(X)-1), (X) } is a compound literal.


The following will convert a C string to a pascal like string. Note that as a pascal like string, there is no '\0'.

#include <limits.h>
#include <stdlib.h>
#include <string.h>
char *pstring_convert(char *s) {
  size_t len = strlen(s);
  assert(len <= UCHAR_MAX);
  memmove(s+1, s, len);
  s[0] = (char) (unsigned char) len;
  return s;
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • To be useful in C, even a Pascal-type string needs to store a null terminator so it can be passed to functions that expect C strings, not Pascal strings. Things like system calls, for example (`open()`, et al) demand it. The majority of the C library expects C strings. Having to copy the Pascal string to create a C string would be unnecessarily heavy-duty work. You don't have to account for the null in the length byte (it's simply there; the length simply records the number of characters up to the null). You do (IMNSHO) have to add it. Knowing the length has important advantages. – Jonathan Leffler Aug 30 '17 at 18:29
  • @JonathanLeffler Fair enough. OP did comment "I need to communicate with a system that uses Pascal like strings" so this code does that. I suspect OP needs some p-string constants. Had OP requested an object that worked both like a Pascal-like string and a C string, your idea is certainly valuable with the more flexible object. Note that conversion from C string to a common pascal_and_C like string incurs additional memory management issues not needed with only a pure C to/from pascal conversion. – chux - Reinstate Monica Aug 30 '17 at 18:39
  • I don't get the point of `sizeof(X)-!!sizeof(X)`. If `X` is a string literal, `sizeof(X)` will be at least 1 (even if `X` is `""`), so `!!sizeof(X)` will always be 1. But that means that if `X` is `""`, then the array will be declared as `s[0]`, which is not valid (although gcc accepts it). – rici Aug 31 '17 at 06:48
  • I think it works as needed, but my compiler only support C89 and C99, and as I understand generics was introduced in C11. Correct me if i'm wrong. But anyway, it does not work with my compiler. – Tajen Aug 31 '17 at 07:02
  • @rici Agreed that `sizeof(X)-1` is sufficient. The `!!sizeof(X)` was an effect of how code was developed. – chux - Reinstate Monica Aug 31 '17 at 12:58
  • @Tajen Important information that would limit answers like "unable to use a compiler that conforms to the current standard and need a C.xxx solution" should be part of the post. – chux - Reinstate Monica Aug 31 '17 at 13:00
0

You could split the macro into two:

#define PASCAL_STRING_TYPE(size) struct { unsigned char len; char content[(size) - 1]; }
#define PASCAL_STRING_INIT(str) { .len = sizeof(str) - 1, .content = (str) }

Then use it like so:

static const PASCAL_STRING_TYPE(100) foo = PASCAL_STRING_INIT("foo");

struct bar {
   int answer;
   PASCAL_STRING_TYPE(100) question;
};
static const struct bar quux = {
    .answer = 42,
    .question = PASCAL_STRING_INIT("The Answer")
};

(Not tested.)

Ian Abbott
  • 15,083
  • 19
  • 33
  • Good idea. But i cannot have a `extern PASCAL_STRING_TYPE(30) some_string;` because the compiler sees it as to different declarations which it thinks are incompatible. But for statics it works as I need. – Tajen Aug 30 '17 at 14:37
  • True, but you could do `typedef PASCAL_STRING_TYPE(30) pstr30;` and `extern pstr30 some_string;`. – Ian Abbott Aug 30 '17 at 14:46