4

Currently, I have the following block of code to make safe string copying (it works):

#define STRCPY(dst, src) do { assert(((void*)(dst)) == ((void*) & (dst))); \
                              strlcpy(dst, src, sizeof(dst)); } while (0)

So it accepts the construction like:

const char *src = "hello";
char dest[5];
STRCPY(dest, src); //hell

And denies the following:

void (char *dst) {
    STRCPY(dst, "heaven"); //unknown size of dst
}

The problem is that the block of code creates an assertion. Is there a way to perform this check on compilation time?

So I want to have the error on compilation (like creating an array with negative size) instead of crashing code if it possible.

madjaoue
  • 5,104
  • 2
  • 19
  • 31

4 Answers4

5

If standard C is available, then you can do this:

#define STRCPY(dst, src)                                        \
  _Generic(&(dst),                                              \
           char(*)[sizeof(dst)]: strlcpy(dst,src,sizeof(dst)) )

Explanation:

You can't use a _Generic expression on an array type, because it is not one of the special cases that is exempt from the "array decay" rule (C17 6.3.2.1 §3). So by simply using _Generic((dst), ... in my example, dst would end up as a char* when the expression is evaluated, and then we would lose the information of its original type.

But if we take the address of the array using &, we do utilize one of those special cases and the array decay doesn't happen. Instead we end up with an array pointer, meaning that _Generic will have to check for an array pointer of the expected type and size: char(*)[sizeof(dst)].


As a side-note/safety concern, I never use do-while(0) macros and discourage them, but that's another story.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • What is the concern with do-while(0) ? I'm interested in that story :) – Michael Nov 25 '20 at 20:24
  • 1
    @Michael I wrote a little story about it [here](https://software.codidact.com/questions/279523). – Lundin Nov 26 '20 at 08:28
  • Thanks. So '{ ...}' (without do-while ) helps enforce the coding style of 'always use braces'. (but there are still corner cases like nested 'if-else' as pointed out by 'anatolyg‭' – Michael Nov 26 '20 at 17:35
1

I've found that my post was closed as duplicated for a while and followed the link mentioned. The solution is only for GCC, but it's fine for me, because I have night builds on GCC too. So the pre-draft of the code is:

#if defined(__GNUC__)
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
#define STRCPY(dst,src) do{__must_be_array(dst);strlcpy(dst, src, sizeof(dst));}while(0)
#else
#define STRCPY(dst,src) do{strlcpy(dst, src, sizeof(dst));}while(0)
#endif
  • If non-standard gcc is acceptable you should mention this in the question and also tag it with the gcc tag. Anyway, non-standard trickery like this should no longer be necessary since C11. Plus the compiler error from _Generic is somewhat more readable ("selector of type char** is not compatible with any association") than "could not declare a negative size bit-field" or whatever this macro boils down to. – Lundin Dec 18 '18 at 15:37
1

For compile-time assertion on whether or not dst is an array, I would use @Lundin solution (or _Static_assert) in case C11 is available.

Else, I would use the following:

#define BUILD_BUG_ON_NON_ARRAY_TYPE(e) (sizeof(struct { int:-!!(((void*)(e)) != ((void*) & (e))); }))

Which basically takes your compile-time evaluated expression ((void*)(dst)) == ((void*) & (dst)) but instead of using it in run time assert just use it in a compile-time assertion manner.

So, overall I would change your macro into:

#define STRCPY(dst, src) do { BUILD_BUG_ON_NON_ARRAY_TYPE(dst); \
                              strlcpy(dst, src, sizeof(dst)); } while (0)
izac89
  • 3,790
  • 7
  • 30
  • 46
0

If you want to check it the only way I can think of is:

assert((sizeof(dst)) != (sizeof(void*)));

but it only work if the size of the array is different than the size of the pointer on OPs system

0___________
  • 60,014
  • 4
  • 34
  • 74
  • 1
    This will fail if checked in the scope where the array is passed as pointer which is usually the problematic case. – Alex Lop. Dec 18 '18 at 11:31
  • @AlexLop. This is not the case here. There is nothing like `passing` in definitions - only replacement., – 0___________ Dec 18 '18 at 11:34
  • 1
    But what you suggest doesn't answers the OP issues. The problem here is that `dst` is a parameter inside a function and it is a pointer. So your assert will always fail it. The point is there is **no way** to check if a pointer is an array. – Alex Lop. Dec 18 '18 at 11:54
  • @AlexLop. but he asks about the macrodefinition not the function. It is something completely different. – 0___________ Dec 18 '18 at 12:00
  • I think you misunderstood the question. The OP specifically indicates the case for which his macro doesn't work. – Alex Lop. Dec 18 '18 at 12:12
  • @P__J__ Thanks, but that's not an answer (I've already thought about comparing size). And sure, I'm looking for a marco, not an assert. So the solution you described should looks something like: `#define CHECK_ARRAY(dst) do{char x[(sizeof(dst)) == (sizeof(void*))?-1:1];}while(0)` Solution is not portable and does not work on arrays with the same length as machine word – Vladimir Botov Dec 18 '18 at 12:19
  • @VladimirBotov there is no other way. You need to change your idea. – 0___________ Dec 18 '18 at 12:52