4

Lets imagine that we have function that should return two return values. For instance we have some function that returns char* and its length. Char is allocated within that particular function.

I can imagine following ways of doing that:

int foo(char **result);       // Passing pointer to char*, returning int
char* bar(int *len);          // Passing pointer to int, returning char*
struct char_and_len foobar(); // Returning struct that contains both values

Is there any other ways to implement multiple values and what's the most effective way to do that?

I'd really appreciate detailed explanation, considering performance, memory alignment or any other hidden C feature.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Sigurd
  • 7,865
  • 3
  • 24
  • 34
  • 1
    Why get any more clever than the options you've provided? You'll only be obfuscating your code by doing so. – user229044 Jan 03 '14 at 20:16
  • @meagar. Why? I'm curious what's the other ways of doing that. There might be some really interesting reasons of doing that. Moreover, I have strong feeling that in C different ways of doing the same thing might have different effects and considerations. – Sigurd Jan 03 '14 at 20:19

7 Answers7

3

There are two cases here. If you are working in a codebase/framework (e.g. Glib) that has a standard string struct used throughout the entire application, then use your third option:

struct string function();

If your codebase is not using one standard struct everywhere for strings, I would advise against using a struct. The hassle of converting back and forth is not really worth it.

Otherwise, the convention (at least that I've seen) is to return the char* and have the length as a pointer parameter:

char* function(int* length);
Linuxios
  • 34,849
  • 13
  • 91
  • 116
  • Could you please elaborate why `char* f(int*)` vs `int f(char **)` – Sigurd Jan 03 '14 at 20:25
  • @Sigurd: At least in most of the code and library functions I've seen, `char**` is used when the caller is responsible for memory allocation and has already determined the length, or is providing a maximum length. For example, in the Linux system call `read(2)`, you provide a preallocated buffer and a maximum data length for that buffer. – Linuxios Jan 03 '14 at 20:28
  • "Otherwise, the convention is to return the char* and have the length as a pointer parameter:" -- asserting that the convention **is** this is a tad bit harsh. There are other, equally valid conventions as well. –  Jan 03 '14 at 20:38
1

Another way:

void foo(char **result, int *len);

The worst in my opinion is:

struct char_and_len foobar();

The one I prefer is the one I showed you, because I don't like to mix return values in both arguments and effective return.

bolov
  • 72,283
  • 15
  • 145
  • 224
1

You can just return an array and wrap it in a struct:

typedef struct {
    char *strings[2];
} RetType;

RetType func()
{
    return (RetType){ { "foo", "bar" } };
}

Another idiomatic solution is C to pass an array to your function and have it filled:

void func(char *strings[2])
{
    strings[0] = "foo";
    strings[1] = "bar";
}

A third solution is to return one value and pass the other one by pointer (though this is more meaningful if you have values of distinct types):

char *func(char **outparm)
{
    *outparm = "foo";
    return "bar";
}

Also, excuse me for not being const-correct.

  • I might be not too clear in my question. I need to return int and char *. Let's imagine that char is not terminated with \0 so we do have to return its length. – Sigurd Jan 03 '14 at 20:27
  • @Sigurd Then you can change your `struct` to contain a `char *` and an `int`. And you can change one of the return values in the third example to an `int` too. And forget about the second approach. –  Jan 03 '14 at 20:30
  • I know how to return multiple values :) My question was what's the most effective way and why. – Sigurd Jan 03 '14 at 20:33
  • @Sigurd Effective in what sense? I've listed all the practically possible solutions. Pick one. It really doesn't matter. –  Jan 03 '14 at 20:37
1

My favourite would be

void foobar(struct char_and_len*);

For the following reasons:

  • Only a single parameter needs to be passed
  • return value / out parameter aren't mixed
  • return values can be ignored, especially when the return value needs to be deallocated again this can be a serious source of programming errors. Out parameters cannot be ignored.
  • Having a pointer to the struct avoids too much copy operations. Only one pointer needs to be provided to the function.
  • This way the caller of the function can decide where the struct char_and_len is stored (on the heap, stack) while when using return values the data needs to be put on the stack at least temporarily
gerrit zijlstra
  • 766
  • 4
  • 8
  • 1
    If you have a struct made for this purpose why not return the whole struct by value. – this Jan 03 '14 at 20:29
  • While in general I would agree with you, because we're dealing with strings here don't you think it's a problem to be defining a string "type" that has to be converted to and from? – Linuxios Jan 03 '14 at 20:29
1

Use a string.

For the specific example you cite, remember that length is implicitly or explicitly a property of any string type. For example, C-style strings are null-terminated, so even though there's not an explicit length the caller can still determine the length of the string. Pascal-style strings include length as the first byte. char* isn't necessarily a string, it might be a plain old text buffer where you do need the length. But the point of string types is to avoid the need to pass data and length separately.

More generally...

A function can only return one value, so if you need to return more than that you need to either package everything up into a single value using a struct, or pass a pointer (or pointers) to a location to receive the results. Which method you use depends on the circumstances. Do you already have a struct defined for the data that needs to be returned? Is the caller likely to have an existing object that could receive the results? There is no best method, only an appropriate method for the situation.

Caleb
  • 124,013
  • 19
  • 183
  • 272
0

The best way is always work with pointers. In C, every function duplicate the arguments passed in a different zone of memory. The best way is the second one of the three that you have posted in term of performance.

But I would pass as argument a pointer to a struct wich contains both, lenght and string with a void return.

Abend
  • 589
  • 4
  • 14
  • Strings in C are always pointers, no matter whether you pass the pointer itself by reference or value. – Linuxios Jan 03 '14 at 20:30
  • Yep, but he has an int too. – Abend Jan 03 '14 at 21:33
  • An int is typically four bytes. On a 32-bit system, so is a pointer. On a 64-bit system, the pointer (8 bytes) will actually be *larger* than the `int`. – Linuxios Jan 03 '14 at 21:41
  • On a 64-bit system int and *int have the same size, 4 bytes not 8. I have just checked that (On 32-bit system it is 2 bytes). So in that case you are not going to notice any improvement on performance :P For that reason I said that I would use a single pointer to a struct with both data type in it. – Abend Jan 03 '14 at 22:04
0

In C it isn't "normal" to return two values. That means that as you already did you could create a struct to return the result. That is the formally correct way of doing it, but not the simplest one and it is coumbersome. So I would totaly forget that solution.

Generically, the other way of returning results is by passing arguments by reference (in Pascal VB etc ). In C there is not the possibility to pass them by reference instead a pointer is passed. But in C++ there is the possibility to pass the variable by reference ( that simply means passing the pointer but using the variable ).

So I believe the simplest way of doing what you need is to define:

char* bar(int *lenP); //

now you have the result of a function that could return two results:

like if it was defined as pseudo syntax (s,l)=bar();

Additionally you could also use:

void bar(char * *s,int * lenP); // that case treats equally the too arguments.

In C++ I would use the by reference approach because while being the same from the practical point of view (what the processor does) it is simpler for the programmer.

George Kourtis
  • 2,381
  • 3
  • 18
  • 28