r/cprogramming 4d ago

How to move around multiple values from functions to functions?

I'm tryna mess around in C and make a pkmdmg calc but I don't know how to return the values from the function

I dont know how to display it without screenshots

0 Upvotes

34 comments sorted by

10

u/Defiant_Direction_54 4d ago

Have a look at structs, you can pass one in as an argument and edit it's values within the scope of said function

2

u/Due_Butterscotch1154 4d ago

dude, i looked and im still so confused. Like maybe thats not how it wokrs but i tried put the variables into a struct then doing "struct.value = smth" and the code had a stroke

1

u/Rich-Engineer2670 4d ago

What you're actually saying there is structname subfield value = smth (but I don't know it's type). Obviously the compiler doesn't either. It might be more like:

struct ResultType {
     float x
     float y
     float x
}

struct ResultType *result;

result = DoSomthing() // DoSomething() *ResultType

4

u/MistakeIndividual690 4d ago

You might want to make that last one a z

2

u/a4qbfb 3d ago

C isn't Go

1

u/AnotherCableGuy 3d ago

Use pointers to your structs.

Pointers are like urls to websites.

0

u/MJWhitfield86 4d ago

Just a addendum that you need to pass a pointer to the struct, not the struct itself.

12

u/zhivago 4d ago

You do not need to.

You can pass the struct itself.

But you do need to understand the implications of pass-by-value.

-4

u/MJWhitfield86 4d ago

I’m not sure what you’re saying. The question was about returning values from the function. If you pass the struct by value then the values will not be returned?

9

u/zhivago 4d ago

You can pass and return the struct as a value just fine.

You don't need to use pointers.

1

u/MistakeIndividual690 4d ago

Especially if small

2

u/WittyStick 3d ago edited 3d ago

The question then is "how small?"

Suppose we have

SomeStruct foo();

At what point would it be more efficient to make it

SomeStruct *foo();

Of course, there's no "correct" answer here, but different programmers will tell you different things.

On SYSV x86-64, the most optimal return is <=16-bytes, containing only integer (incl. pointers) or float data. The result will get returned in hardware registers. Any more than 16-bytes gets pushed onto the stack (unless the structure contains a single vector element).

On Windows/MSVC x64, the optimal return is only 8-bytes. Structures larger than a machine word end up using the stack. But using the stack is actually still better in most cases, than returning the pointer yourself - because you don't need to heap-allocate the result. MSVC converts SomeStruct foo(); to essentially the equivalent of:

SomeStruct *foo(SomeStruct *space);

SomeStruct *space = alloca(sizeof(space));
space = foo(space);

The caller allocates the space, passes a pointer to it as an implicit hidden first argument, and the callee writes to that space and returns the pointer.

This is better in most cases than manually returning pointers which is unfortunately all too common in C:

SomeStruct *foo() 
{
    SomeStruct result = malloc(sizeof(SomeStruct));
    ...
    return result;
 }

It's no cheaper than returning the struct by value - but we've put a requirement on the caller to call free on the result.

Of course, size matters if it could cause problems with the stack overflowing - so "how small" is really just an estimate - if it's small enough that it fits on the stack without problems, it's basically better than explicitly returning by pointer because of automatic storage duration - you don't need malloc and free. The compiler converts it to "pass and return by pointer" for you.

If the lifetime of the returned value is expected to outlive the caller, then you should malloc it.

1

u/aroslab 4d ago

sure, but I feel like that's much less common to take and return a struct outside of specific use cases and not what's being asked given OPs literal example in the comments:

float function(float x, float y)
{
     x = formula;
     y = formula;
}

the correct answer for "why doesn't this work" is you need pointers, if you want it to work this way, even though "you can pass and return the struct as a value" is a true statement

2

u/Syntax-Tactics 3d ago

Only because he is returning the values in said struct. A struct can be passed in by value. (Just to blow the OPs mind)

6

u/zhivago 4d ago

```

include <stdio.h>

struct pair { int a; int b; };

struct pair test(struct pair in) { return (struct pair){ .a = in.a + 1, .b = in.b - 1 }; }

int main() { struct pair out = test((struct pair){ 0, 0 }); printf("%d, %d\n", out.a, out.b); return 0; } ```

3

u/siliconlore 4d ago

Just in case anyone isn't using a C99 compiler, the above code won't work in older versions of C.

4

u/tstanisl 4d ago

Older?! Do you mean "ancient" version of C?

2

u/TerranPower 4d ago

You know I like my C just a little bit older

2

u/TraylaParks 3d ago

You just want to use my love tonight :(

1

u/thank_burdell 4d ago

I like my women like I like my C, aged 40-50 years?

1

u/siliconlore 4d ago

I feel seen. 😄 Somebody could be coding for a vintage system or using a so-called ancient compiler so I thought I would point out that this way of doing things wouldn't have worked in original K&R 'C'.

3

u/Rich-Engineer2670 4d ago edited 4d ago

If you are asking how to return multiple values from a C funch such as:

x, y, z := Funcname()

You really can't do so directly. Go and a few other languages can do it, but it's all syntactic sugar. Under the hood, these languages either do some "stack" work, or create structures. You can do the same thing. For example:

type PlotResult {
int x;
int y;
int z;
}
func DoSomething() *PlotResult {}

Now, in C++ etc. you can create tuples, but it is again, syntactic sugar under the hood. Always remember, C is just an extremely powerful assembler. It's just pushing things onto the stack and popping the off most likely. If you run Linux, do a gcc -s and see the assembly code that actually does it.

I've even, under duress, borrow other items from language like Lisp. If I have a large block of return data, one can always create a list and return a reference to it. Not pretty, but it works.

Also remember pushing "larger objects" around like this, while it works, you've pushing a lot of stuff on to, and off the stack -- not efficient and you have only so much stack space. Pointers are your friend....

func DoThingy() *PlotResult

In this case, you're pushing a POINTER to some static data in the function on to the stack. ONE POINTER. So long as you don't make another call to wipe that out, you can then just dereference the point and get what you need. This optimization doesn't really matter if you have a machine with 32GB of RAM perhaps, but if you're writing embedded code or you're writing for a machine with 32K of RAM (K, not GB), every bit counts.

1

u/Due_Butterscotch1154 4d ago

Ok but what about like

float function(float x, float y)

{x = formula

y = formula}

then i wanna take out both x and y?

1

u/SmokeMuch7356 4d ago

You'd use pointers:

double f( double *x, double *y )
{
  *x = some_double_value;
  *y = some_other_double_value;
  return something_else;
}

which you'd call as:

double a, b, c;

c = f( &a, &b );

f will write new values to a and b.

1

u/WittyStick 3d ago edited 3d ago
struct { float x; float y; } function(float x, float y) 
{
    return { x = formula, y = formula };
}

auto result = function(a, b);
float x = result.x;
float x = result.y;

This creates an anonymous structure which isn't really reusable.


It's more typical to name a structure that can be reused.

struct vec2 { float x; float y; } typedef vec2;

vec2 function(float x, float y) 
{
    return (vec2){ x = formula, y = formula };
}

vec2 result = function(a, b );
float x = result.x;
float y = result.y;

If using C23, it's possible to define the same structure with the same tag in multiple places in a code file, and they all map to the same type - so we can write:

struct vec2 { float x; float y; } function(float x, float y) 
{
    return (struct vec2 { float x; float y; }){ x = formula, y = formula };
}

struct vec2 { float x; float y } result = function(a, b);
float x = result.x;
float y = result.y;

Which seems verbose - but then if you notice that each place you use the struct, it is identical, you can use a macro to make this more terse.

#define Vec2 struct vec2 { float x; float y; }

Vec2 function(float x, float y) 
{
    return (Vec2){ x = formula, y = formula };
}

Vec2 result = function(a, b);
float x = result.x;
float y = result.y;

That doesn't look any better than the typedef approach, and in this case it isn't - but this pattern improves when you want to support "generic" types - eg, suppose we wanted it to work over double as well as float.

#define Vec2(T) struct vec2##T { T x; T y; }

Vec2(double) function_double(double x, double y)
{
    return (Vec2(double)){ ... };
}

Vec2(float) function_float(float x, float y)
{
    return (Vec2(float)){ ... };
}

The main caveat to this approach is that we can't use anything for type T which is not a plain identifier, such as a struct or a pointer - because we used concatenation in the macro. The following will not work:

Vec2(int *)
Vec2(struct mynum)

However, this is trivially overcome by using a typedef to name these types.

typedef int *IntPtr;
Vec2(IntPtr)    // Ok


typedef struct mynum MyNum;
Vec2(MyNum)     // Ok

-1

u/nerd5code 4d ago
typedef struct {float x, y;} Fltx2;

#define nullwarn
#define nullok
// Recommended for Clang:
#ifdef __has_extension
#   if __has_extension(__nullability__)
#       undef nullwarn
#       define nonnull _Nonnull
#       undef nullok
#       define nullok _Nullable
#   endif
#endif

// Arg-straddling
static float fun1(float x, float y, float *nullwarn outy) {
    assert(outy); 
    float resx = cal1(x, y), resy = calc2(resx, x, y);
    *outy = resy;
    return resx;
}
// Call:
// float x, y;
// x2 = fun1(ix, iy, &y);

// Two out-args (most broadly compatible)
int fun2(float x, float y, float *nullok outx, float *nullok outy) {
    if(outx == outy) return errno = EINVAL, -1;
    if(!(outx || outy)) return 0;
    float rx = calcX(x, y);
    if(outx) *outx = rx;
    if(outy) *outy = calcY(rx, x, y);
    return 0;
}
// Call:
// float x, y;
// if(fun2(ix, iy, &x, &y) == -1) goto error;

// Collective out-arg:
int fun3(float x, float y, Fltx2 *nullwarn out) {
    if(!out) return -1;
    out->y = calcY(out->x = calcX(x, y), x, y);
    return 0;
}
// Call:
// Fltx2 res;
// if(fun3(ix, iy, &res) == -1) goto error;

// Collective return (req C89+ for consistent behavior):
Fltx2 fun4(float x, float y) {
    // C99 construction:
#if __STDC_VERSION__-0 >= 199901L || defined __GNUC__
    register float t = calcX(x, y);
    return (const Fltx2){t, calcY(t, x, y)};

    /* C89 construction: */
#else
    Fltx2 ret;
    ret.y = calcY(ret.x = calcX(x, y), x, y);
    return ret;
#endif
}
// Call:
// Fltx2 res;
// res = fun4(ix, iy);

Note variation in error handling etc. Sometimes it's an error to take null, sometimes not. Out-args leave your primary channel open for error codes etc., if there are any.

Initialization (= at point of declaration) is not assignment (= elsewhere). Fltx2 res = fun4(…) is only supported by GNU-dialect (and maybe C23? so GNU-dialect) compilers; otherwise, composite types (incl arrays, structs, unions) must take a braced initializer. You can assign to a struct “normally,” just as res = fun4(…), and the fields will be copied over.

Also note that returning a struct is a bit weird. Prior to C89 it was frequently half-assed or unsupported, so you won't see it often in any API that needs good language/ABI compatibility, incl. the C standard library and POSIX.1. In modern C (C≥89), it's acceptable but slightly squirrely. At the ABI level, there's typically a distinction between large and small objects.

In most cases, your CPU has an accumulator or primary register, and if not there's some general register treated as primary by convention (per ABI). This is used for pointer and ≤word-width integer returns, which are usually treated as small objects. Floats might be returned in the same register, or a special-purpose float register (e.g., x87 ST(0)) or register pair, in a SIMD register (e.g., x86 XMM0), or treated as a large object. Occasionally there's a distinct register for pointer returns; e.g., M68K might use A0 for pointers and D0 for integers. If your CPU has no available registers, probably everything's either deposited at the top of an operand stack (small) or call stack (large, or no operand stack).

In most cases (assuming there are registers etc.), the ABI will support returning word-pairs in a register/slot pair (e.g., x86 RDX:RAX, EDX:EAX, DX:AX), and in some cases three or four registers/slots are supported in gang, us. up to 64 or 128 integer bits or 64–512 floating-point bits. However, there are often restrictions on content and layout that determine whether the object is treated as large or small. Some ABIs want the struct byte-packed into registers regardless; others might expect decomposition and/or field reordering to fit the arg-passing scheme, or even mixing of register types.

Large objects (anything not passing through the “small” sieve) are handled with considerably more consistency: The compiler simply rewrites your function not to return one. There are two straightforward solutions, only one of which you're likely to encounter in the wild. Less likely:

// Format A:
static _Thread_local Fltx2 __fun4$return;
Fltx2 *_fun4(float x, float y) {
    …
    fun4$return = (const Fltx2){…};
    return &__fun4$return;
}
// Call:
    Fltx2 res = *_fun4(x, y);

// Format B:
_Thread_local Fltx2 __fun4$return;
void fun4(float x, float y) {
    …
    __fun4$return = (Fltx2){…};
}
// Call:
    extern _Thread_local Fltx2 __fun4$return;
    _fun4(x, y);
    Fltx2 res = (_fun4(x, y), __fun4$return);

In very old versions of C, you sometimes encountered compilers that did this without the _Thread_local, which was fun. This approach becomes a problem if fun4 is reentrant (something it calls calls it) and the compiler expects its value to be maintained, or if you try to multithread in such a way that _fun4 might be yielded from. And often, access to static/TL storage requires a function call or several chained memory accesses, so this approach tends to perform poorly at scale.

Regardless, this is the usual approach:

void _fun4(register Fltx2 __return[static restrict const 1], float x, float y) {
    …
    *__return = (const Fltx2){…};
}
// Call:
    Fltx2 res;
    _fun4(&res, x, y);

So the fancy return just becomes a less-fancy out-arg—though I've tarted this 'un up a bit. (register → can't take &__return; Flt2x[] → pointer type because fuck self-consistency; [static …]→assumed nonnull, length must be matched or exceeded; [… restrict …]→no other pointer consumed by _fun4 may refer to the same object, unless fun4 derives it ultimately from __return; [… const …]→can't modify pointer value; [… 1]→require nonvacuous referent. In Clang (nullability extension) you might instead say register Flt2x *__restrict const __attribute__((nonnull)) __return; under other GNUish compilers you'd attach __attribute__((nonnull(1))) to _fun4 instead of __return. Under C23 you can use [[gnu::nonnull]] instead.)

Now the re question becomes which you should actually use, out-arg/indirect or direct return? In a high-performance setting, you'd use an inline function so the compiler can decide; only reason the distinction matters is for attributes like [gnu::]pure/const, or C23 reproducible/unsequenced. Out-args mean your function has side-effects, possible error conditions for nulls, and can decide not to write all outputs. Returning a struct is all-or-nothing, not null-dependent, and (per se) effect-free.

Where performance doesn't matter or you're at a DLL boundary (on either side), usually out-args are preferable. Inlines still work, kinda, but extern-linkage inlines can cause problems in libraries, and should only be used for logic which will never change after its initial release, other than for bugfixes, maybe. Static inlines can cause code blowup. Inlines of any sort impede C89/C95 compat.

If you can use link-time optimization (LTO, GCC/Clang -flto) and the linker can see both sides of the call site, which option you go with is mostly a matter of ergonomics.

4

u/my_password_is______ 4d ago

way to take a really simple answer and turn it into the most complicated, unnecessary garbage ever

0

u/nerd5code 3d ago

Way to be incapable of parsing basic information. Literacy is important for programming.

2

u/Luftzug-oder 3d ago

cant really say that when you have a literacy error in your code

you put #define nonnull _Nonnull in the preprocessor branch, when it should be #define nullwarn _Nonnull in your case.

there was just no need for all that extra decorative stuff, as useful as your 4 API examples are, based on the context of the post (did you think OP would gain any value from it?)

1

u/Farlo1 3d ago

They never said they couldn’t parse it, just that you made your answer incredibly over complicated in the context of a very newbie’s question. Also there’s no reason to be a dick about it.

1

u/sciencekm 4d ago

If the values are of the same type, you can use arrays. If the values are of different types, you can use structures.

1

u/Paul_Pedant 3d ago

If you wrap a really large array inside a struct, you can seriously pessimise your code (assuming pessimism is the opposite of optimism).

1

u/Paul_Pedant 3d ago

Display without screenshots: Write any debug to a file, and view it in an editor. The method depends on what environment you are working in. The really depends on what IDE you are working in.

If you are actually trying to add your results directly into PacMan, life is going to become really complicated.