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.

4
  • sizeof *dst is unknown in your case, so what do you expect? And the check is done at compile time. Did you mean: Is there a way to perform this check on run time? Commented Dec 18, 2018 at 11:02
  • a compilation error if a ponter passed (not an array) Commented Dec 18, 2018 at 11:03
  • Please edit your question and put all clarifications there. Commented Dec 18, 2018 at 11:05
  • 1
    this not the dup. OP wants to know if the parameter of the macro is pointer or not compile time Commented Dec 18, 2018 at 11:08

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

3 Comments

What is the concern with do-while(0) ? I'm interested in that story :)
@Michael I wrote a little story about it here.
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‭'
2

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)

Comments

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

1 Comment

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.
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

7 Comments

This will fail if checked in the scope where the array is passed as pointer which is usually the problematic case.
@AlexLop. This is not the case here. There is nothing like passing in definitions - only replacement.,
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.
@AlexLop. but he asks about the macrodefinition not the function. It is something completely different.
I think you misunderstood the question. The OP specifically indicates the case for which his macro doesn't work.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.