Page 2 of 2

Re: My first code ever in C, how do you rate it?

Posted: Mon Aug 10, 2026 3:31 pm
by TryThings
Not cleared. Those variables are part of the stack, and when a task exits that stack is handed back to the heap allocator...
I see, so if I understood correctly, it doesn't manually zero the bits in memory, it just reports as "free" the memory bytes to be used for other stuff.
This means they may be overwritten, maybe not, undefined behaviour.

Whatever t points at (or whatever handle t contains) is passed on into the api, but not the memory location of t itself. This means t is a pointer or handle, you can let it go out of scope safely. Compare to:

Code: Select all

some_var t;
initialize_thing(&t);
do_thing_with_thing(&t, 123);

A pointer to t gets passed to all functions, meaning the memory at t is actively used by the API. Letting t go out of scope = stuff might break.
I think I understand, if my function used just a local copy of the value, I could free the memory allocation of the original without problems.
In my case however xTaskCreate binds my function to accept only a void* argument, so I don't have any choice but passing a pointer to the original like this, with EnArgs being the passed argument and declared at file scope:

Code: Select all

void enabler_function(void *pvParameters){
	
	struct enabler_args *args = pvParameters;
	
	bool status = 0; //0 is disabled, 1 is enabled
	
	while(1){
		
		xSemaphoreTake(enabler_button_semaphore, portMAX_DELAY);	//wait until the interrupt is triggered
		
		if(status == 1){	//if the cycle is enabled at the moment of pressing
			
			blink_manager_disable(args->cycle_handle);	//disabled the possibility to toggle the cycle
			status = 0;
			gpio_set_level(args->LED, 0);	//status LED is OFF
			printf("Cycles are disabled\n");
			xSemaphoreTake(enabler_button_semaphore, pdMS_TO_TICKS(250));	//button debounce
		}
		
		else{	//if the cycle is already disabled at the moment of pressing
			
			blink_manager_enable(args->cycle_handle);	//enables the possibility to toggle the cycle
			status = 1;
			gpio_set_level(args->LED, 1);	//status LED is ON
			printf("Cycles are enabled\n");
			xSemaphoreTake(enabler_button_semaphore, pdMS_TO_TICKS(250));	//button debounce
		}
	}
};

I've also found a workaround, copying the EnArgs into a function-scope struct.
This way I can keep EnArgs at app_main() scope and allow it to get cleared.

Code: Select all

void enabler_function(void *pvParameters){
	
	struct enabler_args LocArgs = *(struct enabler_args*)pvParameters;
	
	bool status = 0; //0 is disabled, 1 is enabled
	
	while(1){
		
		xSemaphoreTake(enabler_button_semaphore, portMAX_DELAY);	//wait until the interrupt is triggered
		
		if(status == 1){	//if the cycle is enabled at the moment of pressing
			
			blink_manager_disable(LocArgs.cycle_handle);	//disabled the possibility to toggle the cycle
			status = 0;
			gpio_set_level(LocArgs.LED, 0);	//status LED is OFF
			printf("Cycles are disabled\n");
			xSemaphoreTake(enabler_button_semaphore, pdMS_TO_TICKS(250));	//button debounce
		}
		
		else{	//if the cycle is already disabled at the moment of pressing
			
			blink_manager_enable(LocArgs.cycle_handle);	//enables the possibility to toggle the cycle
			status = 1;
			gpio_set_level(LocArgs.LED, 1);	//status LED is ON
			printf("Cycles are enabled\n");
			xSemaphoreTake(enabler_button_semaphore, pdMS_TO_TICKS(250));	//button debounce
		}
	}
};


Now it comes down to choose one of the two version, I think allowing EnArgs to get freed upon app_main()'s closure would be nice, less memory occupied for nothing, but I will still have the same duplicated values inside the initialised tasks anyway.

Initially I thought:
if I moved the initialization function of my API into the enabler_function, I could save up some memory for the heap allocator, passing to my API's functions only the pointer to the function-scope handle and allow the tasks to use that one.

But now I realize that EnArgs, which contains handles, GPIOs, bools... still has to be stored somewhere.
So as long as I only use pointers to the original WITHOUT making copies, the memory used will always be the same, and at this point it's better to keep EnArgs at file scope, work with pointers and make the project easier to update, improve, implement as a piece somewhere else...

With all the above reasoning I thought I finally understood my last issue as well, but trying to replicate how the Espressif's API sends data here and there proved me wrong.
I've tried to do some experiments:

Code: Select all

//right before app_main()

typedef struct {
		int TestValue;
}TestStruct_t;

void TestFunction(void *TestArgument){
	TestStruct_t LocStruct = *((TestStruct_t *)TestArgument);	//All good here
	TestStruct_t LocStruct2 = TestArgument;	//Tried to do the same as you, IDE reports error "	Initializing 'struct TestStruct_t' with an expression of incompatible type 'void *' [typecheck_convert_incompatible]"
	LocStruct.TestValue =+ 10;
};
};

Code: Select all

//inside app_main()

	//Testing if void* can take also non-pointers
	TestStruct_t TestStruct;
	TestStruct.TestValue=10;
	printf("Initiating test to see if void* can take non-pointers as argument");
	/*
		-The following line returns as error:
		Passing 'struct TestStruct_t' to parameter of incompatible type 'void *'
		main.c:23:25: note: passing argument to parameter 'TestArgument' here [typecheck_convert_incompatible]

		-And instead returns the following if I type the argument as (void *)TestStruct:
		Operand of type 'struct TestStruct_t' where arithmetic or pointer type is required [typecheck_expect_scalar_operand]
	*/
	TestFunction(TestStruct);
As you can see there are various errors impeding me to pass just a struct while my function asks for a void*, so I suppose there's some more complex shadow logic in Espressif's API that I'm failing to replicate OR (likely) I still didn't understand the matter.

If you can help me this last time I'll be grateful, otherwise if the content of the function can't be made public I understand, I'll just use it as it is.

Re: My first code ever in C, how do you rate it?

Posted: Tue Aug 11, 2026 7:37 am
by Sprite
I've also found a workaround, copying the EnArgs into a function-scope struct.
This way I can keep EnArgs at app_main() scope and allow it to get cleared.

Code: Select all

void enabler_function(void *pvParameters){
	
	struct enabler_args LocArgs = *(struct enabler_args*)pvParameters;
That might work, but it's a race condition. You're banking on the fact that you can copy the parameter data before your app_main() function exits and the original data goes out of scope. It's better to make sure that the original data never goes out of scope in the first place. You can do that by making it 'static' like AI stated, making it a global, or as a third option (which may be the cleanest here): allocating data on the heap:

Code: Select all


#include <stdlib.h>
void enabler_function(void *pvParameters){
	struct enabler_args *args = (struct enabler_args*)pvParameters;
        [...]
        blink_manager_disable(args->cycle_handle);
        [...]
        //Optionally, if this task ever ends:
        free(args); //free memory associated with the args struct so it can be reused
        vTaskDelete(NULL); //end this task
}

void app_main() {
    struct enabler_args *args=calloc(1, sizeof(struct enabler_args)); //Give me space for 1 copy of a enabler_args-sized struct
    args->cycle-handle=....;
    xTaskCreate(...., args, ...);
}

With all the above reasoning I thought I finally understood my last issue as well, but trying to replicate how the Espressif's API sends data here and there proved me wrong.
I've tried to do some experiments:

Code: Select all

//right before app_main()

typedef struct {
		int TestValue;
}TestStruct_t;

void TestFunction(void *TestArgument){
	TestStruct_t LocStruct = *((TestStruct_t *)TestArgument);	//All good here
	TestStruct_t LocStruct2 = TestArgument;	//Tried to do the same as you, IDE reports error "	Initializing 'struct TestStruct_t' with an expression of incompatible type 'void *' [typecheck_convert_incompatible]"
	LocStruct.TestValue =+ 10;
};
};
The issue here is that while what you're doing works, the compiler really doesn't like you shoving something that clearly should be an address (a void*) pointer into something that clearly should not be an address (a TestStruct_t). As it's quite unusual to do this (as it's error-prone - what if you add a member to TestStruct_t?), you need to tell the compiler 'yes, I really want you to do this' by casting the void* pointer to what you want it to be interpreted as:

Code: Select all

TestStruct_t LocStruct2 = (TestStruct_t)TestArgument;
Generally, I'd never do this with structs, as it's too easy to accidentally not fit them into the space of a pointer - I'd just pass the raw int into there. (Or, technically even better, an intptr_t; that's always an int that will fit in the size of a pointer.)

Code: Select all

		-And instead returns the following if I type the argument as (void *)TestStruct:
		Operand of type 'struct TestStruct_t' where arithmetic or pointer type is required [typecheck_expect_scalar_operand]
	*/
	TestFunction(TestStruct);
I'd expect that to work, but I guess the C compiler really, really doesn't like you stashing a struct specifically into a void* space. An int (or intptr_t) is a scalar, so I expect that would work.

Honestly, maybe forget about the trick that you can smuggle a value through a void* pointer. It's a bit of advanced hackery; probably safer to just calloc() the space of a struct, then send the address of that, like I showed earlier.