简体   繁体   中英

C: Pass an typedef'd enum value to a function as a pointer

CONCEPT

I have a library that runs some library code and then calls a user callback function with help of a function pointer. As a minimal example, it may be defined as follows:

function_typDef functions[] = {
    { READ_INT      , set_led_pwm, (void*)&pwm_value } };
     ^library func  ^user func    ^parameter

For anyone wondering what this is for: in this case it is used for a menu system. The library provides internal functions like changing menus, printing the current menu or reading user inputs. Every time the library is called, a user callback function may be run after the internal function, with a void-pointer passed as the parameter. The example above changes to input mode, reads a user input, converts it to an INT, stores this int at &pwm_value and then calls set_led_pwm((void*)&pwm_value).

PROBLEM

One of the main functions of a menu is to change between (sub)menus/menu pages. Menus are listed in a menu_menus_type, a typedef'd enum.

typedef enum menu_menus_type{
    MENU_MAIN,
    MENU_SUB1,
    MENU_COUNT
} menu_menus_type; 

To change a menu, the same concept as shown above for the PWM value change shall be used to change the menu:

function_typDef functions[] = {
    { READ_INT      , set_led_pwm, (void*)&pwm_value },
    { CHANGE_MENU   , NULL       , (void*) MENU_SUB1 } };
     ^library func    ^user func   ^parameter

This works perfectly fine on the microcontroller I tested this on. However, the program crashes during unit testing, due to a memory access violation .

QUESTIONS

From what I understand, enum elements are just replaced by integer numbers during preprocessing. The typedef does not have any effect on this, it just tells the compiler to check if the identifier passed is part of the typedef'd enum. Correct?

If the above is correct, (void*)MENU_SUB1 should create a pointer to address 1. This is most likely not a valid address when used with an operating system, that will detect the violation?

When actually changing the menu, the pointer is dereferenced as *(uint8_t*)pointer . Shouldnt this return the value stored at address 1? That would be anything, but certainly not a valid ID for a menu. Why does this work on the microcontroller?

The derreferencing you are using is wrong.

You should NOT derreference it.

In order to get the number, you should just cast it, the reverse way as you used in the functions array:

//Note that there are no pointer casting, nor derreference.
//All you want is to cast a "void pointer" (pointer) to an "int", so just do it:
int myNumber=(menu_menus_type)pointer;

If you derreference it, then you access the invalid memory and you have the crash then.

NOTE: You should cast to the ACTUAL type. You should not cast to an int , uint8_t or any other, since the compiler is free to use any suitable size for the enums , and if it choose a different integer type, then the cast could be incorrect. The correct way is as shown in the example, using a cast directly to the enum type.


the pointer is dereferenced as (uint8_t )pointer. Shouldnt this return the value stored at address 1?

No, you have not stored anything in the pointer. You have set the value of the pointer to point to address 1 . When derreferencing this address, you are trying to read the data in that address, which is not what you want.

You want to recover the value of the pointer, which is what you have set in the first place. So just a cast is enough.


But why did this work on the microcontroller?

Because probably the microcontroller does not have any memory protection, while in a computer you have memory protection (given by the OS).

So, the microcontroller tries to access that pointer anad the (unexisting) memory protection allows it to do so. This is why the tests crashes.

But then, usually, in the low addresses of the microcontrollers is stored the interrupt table, so the addresses at 0x0 and up are usually valid in these devices, thus succeeding the read and not giving any error.

But the read value is not the value you are expecting. It would contain the data in the address 0x1, and not the value 0x1

, the system would try to change to menu ID address of interrupt handler.

This is not correct either.

You are accessing the address "0x1". Assuming a uC with 32 bit pointers, the pointer size is 4, so you are accessing an address beginning in the second byte, not the first (the first handler would be at address 0x0, the second at address 0x4, and so)...

And after accessing the address, you are casting it to uint8_t* , thus saying "at this address, there is contained a value of just one byte size. Then derreferencing it, so you are reading a single byte at address 0x1, which may be whatever, but not an address, just a byte. Probably, by chance, it contains a 0x1 on it, so you have been lucky.

This is the definition of "Undefined Behaviour": anything can happen, even work correctly, but that case is just luck

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM