enums are used to assign values (typically unique values, although you can force specific values if you wish) to meaningful names that make your code easier to understand. They can be a data type of their own if you declare one as a variable, but they don't have to be.
For example, if you wanted to support different sensor types you might declare an enum such as
enum SENSOR_TYPE
{
TEMPERATURE_SENSOR,
PRESURE_SENSOR,
};
the rules are simple,
1. the first value specified has value 0 (unless you force a specific value). In this example TEMPERATURE_SENSOR has value 0
2. all other values are one greater than the previous (unless you force a specific value). In this example PRESSURE_SENSOR has value 1.
So far you haven't used any memory, until you declare an instance of one. e.g.
enum SENSOR_TYPE my_sensor;
The underlying type of an enum is an int, so now you've used however many bytes an int is (for your complier). Although as others have stated, you can usually force it to be smaller than an int, usually by a compiler optimisation setting.
structs are used to group data items that logically go together. e.g.
struct TEMPERATURE_SENSOR_PARAMETERS
{
int i2c_channel;
short int i2c_address;
};
Again, you haven't used any memory until you declare one. e.g.
struct TEMPERATURE_SENSOR_PARAMETERS my_temp_sensor_params;
So my_temp_sensor_params is the size of one int and one short int, however as others have said, you do need to be aware of padding. You may lose some memory due to padding both because of the order you declared the different sized elements within the struct, and depending on what come immediately after the instance of the struct in memory. The compiler will align each element to an address that is divisible by the width of its data type. So, to use the minimum amount of memory, declare the largest data types first, so on and so forth, with the smallest data elements last in the struct.
e.g.
struct bmxConfig {
uint16_t dig_T1, dig_P1, dig_H4, dig_H5;
int16_t dig_T2, dig_T3, dig_P2, dig_P3, dig_P4, dig_P5, dig_P6, dig_P7, dig_P8, dig_P9, dig_H2;
uint8_t dig_H1, dig_H3, digH6;
};
is better than
struct bmxConfig {
uint8_t dig_H1, dig_H3, digH6;
uint16_t dig_T1, dig_P1, dig_H4, dig_H5;
int16_t dig_T2, dig_T3, dig_P2, dig_P3, dig_P4, dig_P5, dig_P6, dig_P7, dig_P8, dig_P9, dig_H2;
};