4

I have the following code:

char* get_address_string(PACKAGE* pkg){
    char *c;
    sprintf(c, "%02x:%02x:%02x:%02x:%02x:%02x", pkg->address[0], pkg->address[1], 
        pkg->address[2], pkg->address[3], pkg->address[4], pkg->address[5]);
    return c;
}

The code works fine. However, I know this is not the proper way to return a string in C. I am receiving the warning "c is used uninitialized in this function".

What is the proper way to write this function in C?

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
mstagg
  • 507
  • 4
  • 16
  • First of all it **must** crash because you're writing unallocated memory deferencing an uninitialized pointer. Proper way? Hard to give a *general* rule. Like this you leave responsibility to free memory to caller (but you allocate it), it *may* be error prone and cause of leaks. The other way is to have output buffer as input argument. Often it has better performance (buffers may be reused or even stay in stack), it's less error prone but it's also less *natural* and more verbose. – Adriano Repetti Jul 14 '15 at 16:15
  • "The code works fine." - that's not really possible. – Daniel Kamil Kozar Jul 14 '15 at 16:17
  • First, it 100% does not crash. However, having an output buffer as input should work for what I need. – mstagg Jul 14 '15 at 16:19
  • If it doesn't crash...trust us it's just by case. Never heard about *demons flying out of your nose*? – Adriano Repetti Jul 14 '15 at 16:20
  • @Adriano Repetti detail: The must of "First of all it must crash" implies a defined behavior. Certainly crashing is a typical result - and a a fortunate one unlike in OP's case it _appears_ to work fine. – chux - Reinstate Monica Jul 14 '15 at 17:43
  • Yes, it may even *work* even if I did bet in debug variable is zeroed (is it?) and it had to trigger an access violation. Kind of *lucky* even if compiler emitted tons of warnings for this. – Adriano Repetti Jul 14 '15 at 18:04
  • @AdrianoRepetti: "it must crash ...". No, that is the major problem with _undefined behaviour_: Anything can happen. Worst case is nothing shows up. – too honest for this site Jul 14 '15 at 20:57
  • @AdrianoRepetti: Variables are not zeroed-out in debug automatically. But the compiler should warn when using a non-initialized variable. That's why I tell my students first to enable all (actually most) warnings. – too honest for this site Jul 14 '15 at 21:00
  • @Olaf I agree to always enable all warnings. MSVC initializes them (in debug build) to a _magic number_ to _help_ debugging. – Adriano Repetti Jul 15 '15 at 08:18

4 Answers4

9

"Proper way to return a string in C" is not truly possible. In C, a string is a character array (up to and including the null character) and arrays, by themselves, cannot be returned from a function.

A function can return pointers. So the usual method of "return a string" it to:

  1. Return a pointer. char *foo1(...) like char *strdup()

  2. Pass in a pointer to a character array and modify its contents. void foo2(char *,...) like int sprintf(char *dest, const char *format, ...)

  3. Combine 1 & 2 char *foo3(char *, ...) like char *strcpy(char *dest, char *src)

  4. Pass the address of a pointer and update that. foo4(char **ptr) like ssize_t getline(char **lineptr, size_t *n, FILE *stream)

The key is that the memory associated with the pointer must be valid after the function is complete. Returning a pointer to a function's non-static memory is undefined behavior. Successful methods include having the calling code pass in the pointer, or the function providing it via memory allocation of pointer to some persistent value like a global variable or string constant.

What is the proper way to write this function in C?

Current design practice encourages functions like #2 & #3 above to also supply a size_t size so the function knowns the limitations of the memory available.

    char *foo2(char *s, size_t size, const pkg_T *pkg) {
      int result = snprintf(s, size, "%02x:%02x:%02x:%02x:%02x:%02x", 
        pkg->address[0], pkg->address[1], pkg->address[2], 
        pkg->address[3], pkg->address[4], pkg->address[5]);
      // encoding error or not enough room
      if (result < 0 || result >= size) return NULL;
      return s;
    }

Another method would allocate memory (I favor the above though). This obliges the calling code to free() the memory.

    #define UINT_MAX_WIDTH (sizeof(unsigned)*CHAR_BIT/3 + 3)

    char *foo2alloc(char *s, size_t size, const pkg_T *pkg) {
      char buf[(UINT_MAX_WIDTH+3)*6 + 1];
      int result = snprintf(buf, sizeof buf, "%02x:%02x:%02x:%02x:%02x:%02x", 
        pkg->address[0], pkg->address[1], pkg->address[2], 
        pkg->address[3], pkg->address[4], pkg->address[5]);
      // encoding error or not enough room
      if (result < 0 || result >= size) return NULL;
      return strdup(buf);
    }
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • I'd say there are certain advantages to allocating memory in-function in this case, as OP obviously intends the string length to be constant. –  Jul 14 '15 at 17:42
  • @Mints97 Should the calling code or this function allocate memory is often a higher level coding strategy - both have merit. To your " in-function" preference, having this function handle it has the advantages of coping with the usual pesky problem of printing `char address[6]; address[0] = 255` and getting the unexpected `"FFFFFFFF"`, when it should have been either `unsigned char address[6];` or `"%02hhx:`. – chux - Reinstate Monica Jul 14 '15 at 17:50
3

c is a pointer, but no memory is allocated. The return value is ok, that's how it can be done in C.

But you need to allocate memory.

Ely
  • 10,860
  • 4
  • 43
  • 64
  • 2
    the return value is not ok if c were local variable like array, e.g char [128] – Giorgi Moniava Jul 14 '15 at 16:42
  • @Giorgi Not sure if I understand. Could you elaborate please? An example would be helpful. – Ely Jul 14 '15 at 16:59
  • 2
    you wrote "return value is ok" - it would not be ok if c was declared like say `char c[128] ="test";` – Giorgi Moniava Jul 14 '15 at 17:03
  • 1
    We don't know how the OP uses it. I expect him to use it correctly. There are many *ifs* out there. Why being so counterproductive? Within the context given I think it is OK. – Ely Jul 14 '15 at 17:31
0

Since c is uninitialized, sprintf writes to an unknown memory location, which leads to unspecified behavior. It might crash immediately, it might not crash at all, or it might crash on some completely unrelated line of code.

You need to initialize the pointer by allocating memory to it with malloc.

char* get_address_string(PACKAGE* pkg){
    char *c = malloc(20);  // enough room for output as 00:11:22:33:44:55 plus null terminator
    if (c == null) {
        perror("malloc failed");
        exit(1);
    }
    sprintf(c, "%02x:%02x:%02x:%02x:%02x:%02x", pkg->address[0], pkg->address[1], pkg->address[2], pkg->address[3], pkg->address[4], pkg->address[5]);
    return c;
}

Note that even though you know ahead of time how much memory you need, you can't set it aside at compile time via an array. This is wrong:

char* get_address_string(PACKAGE* pkg){
    char c[20];    // allocated on the stack, contents unspecified on return
    sprintf(c, "%02x:%02x:%02x:%02x:%02x:%02x", pkg->address[0], pkg->address[1], pkg->address[2], pkg->address[3], pkg->address[4], pkg->address[5]);
    return c;
}

As is this:

char* get_address_string(PACKAGE* pkg){
    char c[20];    // allocated on the stack, contents unspecified on return
    char *p = c;
    sprintf(p, "%02x:%02x:%02x:%02x:%02x:%02x", pkg->address[0], pkg->address[1], pkg->address[2], pkg->address[3], pkg->address[4], pkg->address[5]);
    return p;
}

Since c is allocated on the stack, when get_address_string returns the contents are unspecified, leading again to unspecified behavior.

dbush
  • 205,898
  • 23
  • 218
  • 273
-3

I prefer allocating heap from the caller so that it's clear who should free it.

#include <stdio.h>
#include <malloc.h>

bool GetString(char ** retString, size_t size)
{
    // use size to do range check
    sprintf_s(*retString, size, "blah blah blah");

    return true;
}

int _tmain(int argc, _TCHAR* argv[])
{
    size_t size = 100;
    char *data = (char *)malloc(size);

    if (data)
    {
        GetString(&data, size);

        free(data);
    }
    return 0;
}
Kenny Lim
  • 1,193
  • 1
  • 11
  • 31
  • 2
    Why the double-pointer? Passing a simple `char *` would perfectly do. – alk Jul 14 '15 at 17:02
  • Also the code you are showing a not Standard-C. There nothing like `_tmain()` in C. I did not downvote, btw. – alk Jul 14 '15 at 17:11
  • 1
    What is the point that the return type is bool and it always returns true? – Ed Heal Jul 14 '15 at 17:17
  • you don't have to return but for the sample it's always good to have options to return if the method fail and pass status back. – Kenny Lim Jul 14 '15 at 21:08