4

Is there a better way to temp alloc a string of unknown length in C in a way that doesn't require cleanup?

I currently use the following, which does use alloca/_alloca or insert the name your compiler likes for this function

// OLD
// #define stackdup(s)     \
//    memcpy(memset(_alloca(strlen(s) + 1), 0, strlen(s) + 1), s, strlen(s))

#define stackdup(s)     strcpy(_alloca(strlen(s) + 1), s) // refined per comments

// stackndup and stackmiddup can't use strcpy because they need memset 0 ...

#define stackndup(s,n)  \       
    memcpy(memset(_alloca(strlen(s) + 1), 0, strlen(s) + 1), \
        s, n > strlen(s) ? strlen(s) : n ) 
#define stackmiddup(s,pos,n) \
    memcpy(memset(_alloca(strlen(&s[pos]) + 1), 0, strlen(&s[pos]) + 1), \
        &s[pos], n > strlen(&s[pos]) ? strlen(&s[pos])) )

int main ()
{
    const char *address         = "123 Main Street";
    const char *copy_address    = stackdup    (address);       // "123 Main Street"
    const char *address_123     = stackndup   (address, 8);    // "123 Main"
    const char *address_123x    = stackndup   (address, 55);   // "123 Main Street"
    const char *address_main    = stackmiddup (address, 4, 4); // "Main"

    ...
}

Takes advantage of how memcpy and memset return the dest, formats the buffer one extra byte to provide for null termination.

Can't use it in a loop obviously as it would allocate on the stack again and again.

10
  • You want to declare it globally only ? Commented Jul 9, 2015 at 11:34
  • 3
    How about char address[] = "123 main street";? Why can't you use that? Commented Jul 9, 2015 at 11:51
  • 2
    I'd recommend char address_main[5]; snprintf(address_main, sizeof address_main, "%s", address + 5); . All these macros may seem cute but it makes your code look weird to a maintenance programmer. You could wrap that in a macro if you really want. Commented Jul 9, 2015 at 12:05
  • 1
    of course another option is to not do this in the first place, and change whatever function you are calling to take a pointer and a length, instead of expecting null-terminated string Commented Jul 9, 2015 at 12:06
  • 1
    Just a warning. If a user has control over the string, it's relatively trivial to overflow your stack and crash or if you're threaded overflow the stack and write to some other memory (potentially other threads stack). Never do alloca on a length that you don't have full control over. Alloca is powerful, but it's also very dangerous in some situations. Compilers have options to do alloca safely, but as far as I know no compiler does that by default. Commented Jul 9, 2015 at 13:13

3 Answers 3

1

With a C99 compiler, local arrays can be dynamically sized:

  void demo (const char * original)
  {
     char copy[strlen (original) + 1];
     strcpy (copy, original);

     ...
  }

This works the same way as alloca, but it's cleaner and (more) portable.

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

2 Comments

I agree in principal, but Microsoft Visual Studio is C89 and I want it to work with any mainstream compiler.
" and (more) portable" not quite... C11 made VLA's optional again. C11 compilers may not be able to handle VLA's then, so it's only portable provided you stick to C99 compliant compilers. VLA's would however solve the OP's issue with strings in a loop,as they'd be GC'ed after each termination, and not flood the stack
1

If the length is unknown and can be big, do not use alloca due to stack overflow, use malloc & free instead, or, to be on-topic, there are some libraries for garbage collection, but by using them, performance might be decreased.

If possible, do not allocate strings again. And if is there needed maximum performance, pass their lengths with them except calling strlen().

For example, instead of

const char *address         = "123 Main Street";
const char *copy_address    = stackdup    (address);

Use

char address[] = "123 Main Street";
char* copy_address = address;

In the second case address will be writable, in the first not. By this way, you can make stackndup and stackmiddup a lot faster - if these strings are used one by one in a row (for example, printing or writing them, or copying to structure with static arrays, like struct { char street[32]; }), you can just save index (or better pointer) with original character and write there zero. Note that you will have to make function to recover the original string - set original character back where it was.

EDIT Memory friendly cut & substring functions (for null-terminated strings) doing too same as your example:

#include <stdio.h>
#include <string.h>


/*
 Macros
 */
#define plong long


#define istr_init(i) \
    char* __ptr##i = NULL; \
    char prev##i; \

#define istr_cut(i,s,n) \
    (char*) s; \
    if(n > s##_len) { \
        (__ptr##i) = NULL; \
    } else { \
        __ptr##i = (s + n); \
        prev##i = *(__ptr##i); \
        *(__ptr##i) = 0; \
    }

#define istr_substring(i,s,pos,n) \
    (pos > s##_len) ? "" : (char*) ((__ptr##i) = (s + pos)); \
    if(pos > s##_len) { \
        (__ptr##i) = NULL; \
    } else { \
        if((pos + n) < s##_len) { \
            (__ptr##i) += n; \
            (prev##i) = *(__ptr##i); \
            *(__ptr##i) = 0; \
        } else { \
            (__ptr##i) = NULL; \
        } \
    }

#define istr_back(i) \
    if(__ptr##i) { \
        *(__ptr##i) = (prev##i); \
        (__ptr##i) = NULL; \
    }


/*
 Main
 */
;int main() {
    {
        istr_init(1); //Init - our ID=1
        char* cur;
        char address[] = "123 Main Street";
        unsigned plong address_len = strlen(address); //<name>_len required

        cur = istr_cut(1, address, 8);
        printf("   address_123: [%s]\n", cur);
        istr_back(1);

        cur = istr_cut(1, address, 55);
        printf("  address_123x: [%s]\n", cur);
        istr_back(1);

        cur = istr_substring(1, address, 4, 4);
        printf("  address_main: [%s]\n", cur);
        istr_back(1);

        cur = istr_substring(1, address, 4, 55);
        printf(" address_mainx: [%s]\n", cur);
        istr_back(1);

        cur = istr_substring(1, address, 55, 4);
        printf("  address_null: [%s]\n", cur);
        istr_back(1);
    }
    return 0;
}

Output:

   address_123: [123 Main]
  address_123x: [123 Main Street]
  address_main: [Main]
 address_mainx: [Main Street]
  address_null: []

Comments

0

If C++ is not an option and automatic cleanup is the goal, you could try this library which provides C++ style smart pointers with automatic memory management (on the heap though).

Comments

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.