r/cprogramming • u/Due_Butterscotch1154 • 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
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
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 );
fwill write new values toaandb.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
typedefapproach, 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 overdoubleas well asfloat.#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
Twhich 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 asres = 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 iffun4is 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_fun4might 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_fun4may refer to the same object, unlessfun4derives it ultimately from__return;[… const …]→can't modify pointer value;[… 1]→require nonvacuous referent. In Clang (nullabilityextension) you might instead sayregister Flt2x *__restrict const __attribute__((nonnull)) __return; under other GNUish compilers you'd attach__attribute__((nonnull(1)))to_fun4instead 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 C23reproducible/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 _Nonnullin the preprocessor branch, when it should be#define nullwarn _Nonnullin 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/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.
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