05 Structs
1. Definition and Initialization
1.1 Definition
To define a struct
in C, you use the struct
keyword followed by an optional tag name and a pair of curly braces containing the members (variables) of the struct.
struct MyStruct {
int x;
float y;
};
The tag name is optional. If you don't specify a tag name, you can only use the struct to create a typedef.
struct {
int x;
float y;
} s1; // Valid
struct {
int x;
float y;
} s2 = {1, 3.14}; // Valid
struct {
int x;
float y;
} s3 = {.x = 1, .y = 3.14}; // Valid
1.2 Initialization
After defining a struct
, you can initialize it in various ways:
- At declaration time:
struct MyStruct s1 = {1, 3.14};
- Using designated initializers (C99 feature):
struct MyStruct s2 = {.x = 1, .y = 3.14};
- Member by member after declaration:
struct MyStruct s3;
s3.x = 1;
s3.y = 3.14;
- Without using
struct
keyword (C99 feature):
You can use typedefs to create a new type that is an alias for a struct. This allows you to create instances of the struct without using the struct
keyword.
typedef struct {
int x;
float y;
} MyStruct; // Valid
MyStruct s1 = {1, 3.14}; // Valid
MyStruct s2 = {.x = 1, .y = 3.14}; // Valid
1.3 Accessing Members
Dot Operator
To access members of a struct when you have an actual instance of the struct, you use the dot (.
) operator.
struct MyStruct s;
s.x = 10;
s.y = 20.5;
printf("x: %d, y: %f\n", s.x, s.y); // Outputs "x: 10, y: 20.500000"
Arrow Operator
When you have a pointer to a struct, you can use the arrow (->
) operator to access its members.
struct MyStruct *ptr = &s;
ptr->x = 10;
ptr->y = 20.5;
printf("x: %d, y: %f\n", ptr->x, ptr->y); // Outputs "x: 10, y: 20.500000"
You can also use the dot operator with pointers, but it requires dereferencing the pointer first.
(*ptr).x = 10; // Equivalent to ptr->x = 10
(*ptr).y = 20.5; // Equivalent to ptr->y = 20.5
Both the dot and arrow operators are used frequently in C.
2. Passing to and Returning from Functions
2.1 Passing to Functions
By Value
When you pass a struct by value, a copy of the struct is made, which means modifications inside the function won't affect the original.
void modifyByValue(struct MyStruct s) {
s.x = 99; // Won't affect the original struct
s.y = 99.99; // Same here
}
struct MyStruct example = {1, 3.14};
modifyByValue(example);
By Reference
If you want changes inside the function to affect the original struct, you can pass a pointer to the struct.
void modifyByReference(struct MyStruct *s) {
s->x = 99; // Will affect the original struct
s->y = 99.99; // Same here
}
struct MyStruct example = {1, 3.14};
modifyByReference(&example);
2.2 Returning from Functions
Returning by Value
It's generally less common to return structs by value due to potential performance issues (copying), but it's possible.
struct MyStruct returnByValue() {
struct MyStruct s = {42, 42.42};
return s;
}
struct MyStruct newStruct = returnByValue();
Returning by Pointer
More commonly, you might return a pointer to a struct, especially if it's dynamically allocated.
struct MyStruct* returnByPointer() {
struct MyStruct *s = malloc(sizeof(struct MyStruct));
s->x = 42;
s->y = 42.42;
return s;
}
struct MyStruct *newStruct = returnByPointer();
2.3 Optimization
The compiler might perform Return Value Optimization (RVO) for structs, but it's not a guaranteed behavior in C. In C++, RVO is more standardized, but C leaves more room for compiler vendors to decide on optimizations. The C standard itself doesn't explicitly specify RVO.
However, modern compilers like GCC and Clang often do optimize return-by-value for structs, essentially transforming them into return-by-reference under the hood, thereby eliminating the performance overhead of copying. But it's a good practice not to rely solely on this behavior for performance-critical code, especially in the context of embedded systems where resource usage can be critical.
If performance is a concern, and you're dealing with large structs, you might opt for return-by-pointer or return-by-reference (passing a pointer to the struct into the function and modifying it within) to be on the safe side.
3. Memory Management, Data Packing, and Bit Fields
3.1 Data Packing and Memory Alignment
In C, the way data is packed in a struct depends on the architecture and the compiler. The default behavior aligns data types to their size. For example, an int
would align to 4 bytes in a 32-bit system. However, this can introduce padding bytes, which might not be efficient in memory-critical embedded systems.
Default Layout:
struct example {
char a;
int b;
char c;
};
In a 32-bit system, the size might be 12 bytes (char
+ 3 padding bytes + int
+ char
+ 3 padding bytes).
Packed Layout:
struct __attribute__((packed)) example {
char a;
int b;
char c;
};
Now the size will be 6 bytes (char
+ int
+ char
), but this could be slower on architectures that require aligned access.
3.2 Bit Fields
Bit fields allow you to specify the number of bits you want for struct members. This is particularly useful in embedded systems for memory optimization.
struct packed_example {
unsigned int a : 3;
unsigned int b : 4;
};
Here, the struct size would be a single byte (3 bits for a
+ 4 bits for b
+ 1 padding bit).
Usage:
struct packed_example example;
example.a = 5; // Sets the value to 5 (within the 3 bits)
However, if you try to assign a value greater than the number of bits, the value will wrap around:
example.a = 9;
printf("%d\n", example.a); // Outputs 1
This is because the value is stored in the number of bits specified, and the rest are discarded. So, 9 in binary is 1001
, but since we only have 3 bits, the value is truncated to 001
, which is 1 in decimal.
3.3 Memory Alignment in Embedded Systems
In embedded systems, data alignment can be critical for performance. Misaligned data access can result in CPU exceptions or slower memory access. So, you need to strike a balance between memory optimization and alignment requirements. You can manually align data using __attribute__((aligned(x)))
.
struct __attribute__((aligned(4))) example {
char a;
char b;
};
Here, despite having only two chars, the struct size would be 4 bytes, aligning it to a 4-byte boundary.
3.4 Trade-offs
- Packing: Saves memory but can slow down data access and may lead to misaligned accesses.
- Alignment: Speeds up data access but can consume extra memory due to padding.
- Bit Fields: Extremely memory-efficient but can make data manipulation trickier and often slower.
3.5 Tips for Embedded Systems
- Use
__attribute__((packed))
judiciously, and only when memory savings outweigh the risk of misaligned access. - In real-time systems, always consider the time taken for data access, especially when choosing between packed and aligned data structures.
- Use bit fields for protocol definitions or when you are sure that only a limited range of values will be assigned to the struct members.
4. Opaque Structs
Opaque structs are a technique used to hide the implementation details of a struct
from users of the API. This encapsulation allows you to change the struct's internal structure without affecting the code that uses it. This is useful for maintaining backward compatibility and provides a level of abstraction.
4.1 Basic Concept
An opaque struct is declared in a header file without its members being defined. The actual definition resides in the source file. This means that the size and layout of the struct are unknown to the users of the API, making the struct "opaque."
4.2 Declaration in Header File
// my_struct.h
typedef struct MyStruct MyStruct;
Definition in Source File
// my_struct.c
struct MyStruct {
int x;
float y;
char z;
};
4.3 Using Opaque Structs
Because the size and layout of the struct are unknown, you can't create stack variables of that type or access its members directly:
MyStruct s; // Compiler error
s.x = 10; // Compiler error
You have to define functions to create and access the struct:
// API Functions in my_struct.h
MyStruct* createMyStruct();
void setX(MyStruct* s, int x);
int getX(const MyStruct* s);
And then implement them in the source file:
// Implementation in my_struct.c
MyStruct* createMyStruct() {
MyStruct* s = malloc(sizeof(MyStruct));
s->x = 0;
s->y = 0.0;
s->z = '\0';
return s;
}
void setX(MyStruct* s, int x) {
s->x = x;
}
int getX(const MyStruct* s) {
return s->x;
}
4.4 Advantages
- Encapsulation: Internal structure is hidden, leading to a cleaner API.
- Flexibility: Implementation can be changed without affecting existing code.
- Abstraction: Makes it easier to understand how to interact with the struct.
4.5 Use in Embedded Systems
In the context of embedded systems, opaque structs can be very useful to define interfaces for hardware abstraction layers or middleware without exposing internal details. This allows you to change the underlying hardware or algorithm without affecting the rest of your code. It's especially useful in systems where modularity and ease of updates are important.
4.6 C++ Equivalent
In C++, opaque structs are less common because the language has built-in support for encapsulation through private and protected class members. You can simply declare class members as private, and they won't be accessible from outside the class. This serves a similar purpose to opaque structs in C.
5. Best Practices
5.1 When to Use Structs
In embedded systems, structs are incredibly useful for organizing data in a meaningful way, making code easier to read, debug, and maintain. Use them to group related variables, especially when dealing with hardware registers, sensor data, or communication protocols like UART frames.
For example, instead of having separate variables for temperature
, humidity
, and pressure
, you could have a WeatherData
struct.
5.2 Immutable Structs
Using const
with structs can make them immutable, which can be helpful for safety and debugging. Immutable structs are especially useful for configuration data that shouldn't change after system initialization.
const struct ConfigData config = {
.baud_rate = 9600,
.parity = NONE
};
5.3 Avoid Global Structs
In the context of embedded systems, global variables can introduce subtle bugs and make code hard to maintain. This is even more relevant for global structs, as they often contain multiple pieces of information. Instead, pass structs as parameters to functions or use them as local variables within a relevant scope. If a struct must be shared across multiple files or scopes, consider using extern cautiously, and make sure to document its intended use thoroughly.
6. Q&A
1. Question: What is a struct
in C?
Answer: In C, a struct
(short for structure) is a composite data type that groups variables of different data types under a single name. It allows you to encapsulate a set of data variables, which may be of different types, together in a single unit.
2. Question: How do you define and initialize a struct
in C?
Example:
struct Car {
char brand[50];
int year;
float weight;
};
struct Car myCar = {"Toyota", 2020, 1500.5};
Answer: You define a struct
using the struct
keyword followed by a user-defined name and a set of members enclosed in curly braces. Initialization can be done at the time of declaration, as shown in the example.
3. Question: How would you pass a struct
to a function and return it?
Example:
void displayCar(struct Car car);
struct Car modifyCar(struct Car car);
Answer: A struct can be passed to a function as a parameter, and it can also be returned from a function, just like any other data type. However, be aware that passing large structs by value can be inefficient due to memory copying; it's often better to pass a pointer to the struct instead.
4. Question: What are bit fields in a struct and why are they useful in embedded systems? Example:
struct packedData {
unsigned int flag: 1;
unsigned int id: 4;
unsigned int data: 7;
};
Answer: Bit fields allow for the allocation of a specific number of bits for a struct member, rather than allocating the default byte(s). This can be useful in embedded systems where memory is constrained, and you want to store information in as compact a manner as possible.
5. Question: In embedded systems, what does "data packing" in a struct mean? Answer: Data packing in a struct refers to removing the default padding added by compilers to align data in memory. By tightly packing the struct members, you can reduce memory consumption. However, packed structs may have slower access times on architectures that aren't optimized for unaligned access.
6. Question: How can you prevent a struct from being modified? Example:
void functionName(const struct Car* car);
Answer: By passing a pointer to the struct as a const
parameter to a function, you ensure that the function cannot modify the content of the struct.
7. Question: What is an opaque struct and why might it be useful in embedded systems? Answer: An opaque struct is a struct whose definition is hidden from the user. Only a pointer to the struct is exposed to the user. This allows for data hiding and encapsulation, which can help in creating modular and maintainable code in embedded systems.
8. Question: How can structs be used for serialization in embedded systems? Answer: Serialization is the process of converting complex data types like structs into a byte stream for transmission or storage. In embedded systems, you might serialize a struct into a byte stream to send it over communication protocols. This usually involves iterating over each member of the struct and converting it into a series of bytes.
9. Question: Why might dynamically allocating memory for a struct be avoided in embedded systems?
Answer: Dynamic memory allocation, like using malloc()
, can be risky in embedded systems due to limited memory and the potential for memory fragmentation. In long-running systems, repeated dynamic allocations can lead to system instability. Thus, embedded developers often prefer static or stack-based memory allocation for structs.
10. Question: How do you declare a nested struct? Example:
struct Engine {
int horsepower;
float volume;
};
struct Car {
char brand[50];
struct Engine engine;
};
Answer: A nested struct is declared by including a struct definition within another struct, as shown in the example. The nested struct can then be accessed and manipulated as a member of the outer struct.
11. Question: What's the significance of the volatile
keyword when used with a struct in an embedded system?
Answer: The volatile
keyword tells the compiler that the value of the struct can change at any time without any action being taken by the code the compiler finds. This is crucial for embedded systems when structs represent hardware registers or are modified by external interrupts. Without volatile
, the compiler might optimize out certain accesses, leading to unpredictable behavior.
12. Question: Consider two structs:
struct A {
char c;
int i;
};
struct B {
int i;
char c;
};
Which one may consume more memory and why?
Answer: struct A
is likely to consume more memory due to padding. Most systems will align the int
to a 4-byte boundary, adding 3 bytes of padding after the char
in struct A
. In contrast, struct B
might only add a single byte of padding after the char
to align the struct's size to a multiple of 4.
13. Question: How can you ensure a struct is packed tightly, with no padding added by the compiler?
Answer: Many compilers provide attributes or pragmas for this purpose. For example, in GCC, you can use the __attribute__((packed))
attribute:
struct __attribute__((packed)) NoPadding {
char c;
int i;
};
However, be cautious as packed structs can lead to unaligned memory access, which may be inefficient or unsupported on some architectures.
14. Question: What challenges arise when passing large structs by value to functions? Answer: Passing large structs by value can be inefficient due to the copying of data. It also increases the stack usage which can be critical in embedded systems with limited memory. Additionally, modifications inside the function won't affect the original struct, which may be unexpected.
15. Question: What is the purpose of having anonymous structs within structs? Example:
struct Outer {
struct {
int x, y;
};
char name[50];
};
Answer: Anonymous structs (or unions) within structs allow for more flexible data organizations without having to define a separate named struct type. In the context of embedded systems, they can represent specific configurations of hardware registers or layered data structures.
16. Question: How can you overlay multiple variables in the same memory location using a struct? Answer: This can be achieved using a union inside a struct. Unions allow multiple members to occupy the same memory space. For example:
struct Overlay {
union {
float asFloat;
int asInt;
} value;
char unit[10];
};
17. Question: Why might using bit fields in a struct be problematic across different compilers or platforms? Answer: The implementation of bit fields is compiler-specific. The order of bits and how they are packed can vary between compilers, leading to portability issues. In embedded systems, where precise control over data representation is often required, this unpredictability can be problematic.
18. Question: What do you understand by the term "deep copy" of a struct? Answer: A deep copy means creating a new struct where both the values and any pointers inside the struct point to newly allocated memory, rather than pointing to the original memory locations. It ensures that the two structs are fully independent, and changes to one don't affect the other.
19. Question: Why might using flexible array members in a struct be beneficial in embedded systems? Example:
struct Buffer {
size_t length;
char data[];
};
Answer: Flexible array members allow a struct to effectively contain a variable-length array as its last element. In embedded systems, this can be useful for working with dynamically sized data while avoiding dynamic memory allocation.
20. Question: Consider a struct with an enum
as one of its members. How can this be useful in embedded programming, and what precautions should be taken?
Answer: Using an enum
within a struct can provide a clear way to represent states or modes for devices, operations, or workflows. It can make the code more readable and self-documenting. However, precautions should be taken regarding the size of the enum
, as different compilers might represent it using different data types (e.g., int
or char
), potentially affecting the struct's size and alignment.
21. Question: Why is memory alignment of struct members important in embedded systems? Answer: Memory alignment ensures that struct members are placed in memory addresses optimal for the architecture, leading to efficient memory accesses. Misaligned accesses can cause slower performance or even hardware exceptions on certain architectures.
22. Question: How would you use a pointer within a struct to share data across multiple instances of that struct? Example:
struct SharedData {
int* commonData;
};
Answer: By using a pointer within a struct, multiple instances of the struct can reference the same data in memory. This facilitates data sharing without duplicating the data for each instance.
23. Question: When might you prefer to use a struct over a union, especially in an embedded context? Answer: You'd use a struct when you want all members to have their own storage. Unions, on the other hand, are used when you want multiple members to share the same memory location, typically for representing different types of data in the same space. For example, in embedded systems, unions can be used to access a memory location both as individual bytes and as a whole value.
24. Question: In the context of embedded systems, how can offsetof
macro be useful when working with structs?
Answer: The offsetof
macro, defined in stddef.h
, returns the offset of a member within a struct. This can be useful in embedded systems when interacting with hardware registers or when implementing serialization and deserialization functions.
25. Question: How can you ensure that a struct remains consistent across different compilers or platforms?
Answer: By using explicit padding, specifying memory alignment, and avoiding compiler-specific attributes or extensions, you can help ensure consistent struct layout. Additionally, using fixed-size data types (like int32_t
instead of int
) ensures consistent sizes across platforms.
26. Question: Why might one use function pointers within a struct in embedded systems? Example:
struct DeviceOps {
void (*init)();
void (*read)(char* buffer);
};
Answer: Function pointers within a struct allow for a form of polymorphism in C. This pattern is especially common in embedded systems to define sets of operations for different hardware devices or state transitions.
27. Question: How can embedded developers guard against unintended changes to struct layout?
Answer: Developers can use static assertions (e.g., static_assert
in C11) to ensure at compile-time that struct sizes or member offsets are as expected. Additionally, versioning and clear documentation can alert users to intentional changes.
28. Question: What's the difference between packed structs and aligned structs, and when might you use each in an embedded context? Answer: Packed structs remove all padding, making the struct as small as possible, but potentially leading to misaligned accesses. Aligned structs ensure members are placed at memory addresses optimal for the architecture. While packed structs are used when memory space is a constraint, aligned structs are preferred when access speed is crucial and the architecture is sensitive to misalignment.
29. Question: In systems with both little-endian and big-endian components, how can structs play a role in data interchange? Answer: Structs can be used to represent data formats for interchange. Developers may need to implement byte-swapping routines when copying data into or out of such structs to ensure data is correctly interpreted across endianness boundaries.
30. Question: How do anonymous (or unnamed) unions within structs benefit embedded developers? Example:
struct ControlRegister {
union {
uint32_t fullRegister;
struct {
uint8_t byte1;
uint8_t byte2;
uint8_t byte3;
uint8_t byte4;
};
};
};
Answer: Anonymous unions within structs allow developers to access data in multiple ways. In the example, the same control register can be accessed as a full 32-bit value or byte-by-byte, without requiring a union member name. This provides flexibility in accessing and manipulating data.
31. Question:
struct Device {
char* name;
int id;
};
struct Device dev1 = {"Sensor", 101};
struct Device dev2 = dev1;
dev1.name[0] = 'R';
What's the issue with the above code?
Answer: After the assignment dev2 = dev1;
, both dev1
and dev2
structs have pointers pointing to the same string "Sensor". Modifying the string through one struct (dev1.name[0] = 'R';
) will also change it for the other (dev2
). This might lead to unintended consequences.
32. Question:
struct __attribute__((packed)) Data {
char flag;
int value;
};
struct Data dataInstance;
What could be problematic with the above code on certain architectures?
Answer: The __attribute__((packed))
attribute makes sure there's no padding in the struct. But int
value following a char
may lead to an unaligned memory access for value
. On some architectures, this can cause a crash or reduced performance.
33. Question:
struct Student {
char* name;
int age;
};
void modify(struct Student s) {
s.age = 25;
}
struct Student john = {"John", 20};
modify(john);
What's wrong with the code above?
Answer: The modify
function gets a copy of the john
struct. So, the modification inside the function doesn't affect the original john
struct. After the call, john.age
is still 20.
34. Question:
struct Measurement {
float value;
char unit[3];
};
struct Measurement m = {23.5, "kg"};
What's the potential issue here?
Answer: The unit
array has space for only 3 characters, but we are trying to store "kg" followed by a null terminator, which requires 3 bytes in total. This could lead to unexpected behavior or memory corruption.
35. Question:
struct Timer {
unsigned int start;
unsigned int end;
} __attribute__((packed));
struct Timer* tPtr = malloc(sizeof(struct Timer));
What's the potential issue in the above code?
Answer: While the struct is packed (meaning no padding), the dynamically allocated memory might not be aligned properly for an unsigned int
on certain architectures, leading to potential misaligned accesses.
36. Question:
struct Config {
int enabled;
char* description;
};
struct Config configs[5];
memset(configs, 0, sizeof(configs));
What could be problematic here?
Answer: While zeroing out the memory seems fine, using memset
on an array of structs containing pointers (like description
) might not always initialize those pointers to NULL
on all architectures. Instead, manual initialization in a loop would be safer.
37. Question:
struct Item {
char name[10];
int id;
};
struct Item it = {"Laptop", 101};
printf("%s\n", it.name + 15);
What's wrong in the code above?
Answer: The pointer arithmetic it.name + 15
goes beyond the bounds of the name
array and potentially reads unintended memory, leading to undefined behavior.
38. Question:
struct Settings {
char mode;
double value;
} setting = {'A', 25.5};
char* ptr = &setting.mode;
double retrievedValue = *(double*)(ptr + 1);
What issue can arise here?
Answer: There might be padding between mode
and value
to align value
properly for the architecture. Accessing value
using pointer arithmetic on mode
might not correctly access the start of value
, leading to incorrect results or undefined behavior.
39. Question:
union Shared {
char* name;
int value;
};
union Shared data;
data.name = "Temperature";
printf("%d\n", data.value);
What's problematic in the above code?
Answer: The union allows for shared storage of its members. After assigning to the name
member, trying to access value
would interpret the pointer address stored in name
as an int
. This could lead to nonsensical output or potential issues.
40. Question:
struct Person {
char* firstName;
char* lastName;
};
void setName(struct Person* p, char* first, char* last) {
p->firstName = first;
p->lastName = last;
}
void main() {
struct Person person;
setName(&person, "John", "Doe");
printf("%s %s\n", person.firstName, person.lastName);
}
What could go wrong in this code?
Answer: While the code might seem correct at first, there's a potential issue. The strings "John" and "Doe" are string literals which might be stored in read-only memory. So, assigning them to firstName
and lastName
might lead to undefined behavior.
41. Question:
struct Time {
unsigned int hour;
unsigned int minute;
unsigned int second;
};
struct Time t = {15, 70, 30};
What's wrong with the above code?
Answer: The minute
field is initialized with a value of 70, which is not a valid value for minutes. It should be between 0 and 59.
42. Question:
struct Point {
int x;
int y;
};
void movePoint(struct Point* p, int dx, int dy) {
p->x += dx;
p->y += dy;
}
struct Point p1 = {10, 20};
movePoint(p1, 5, 5);
What's the issue here?
Answer: The function movePoint
expects a pointer to struct Point
, but a struct object p1
is passed directly. This will cause a compilation error.
43. Question:
struct Device {
char name[10];
};
struct Device* createDevice(char* n) {
struct Device dev;
strncpy(dev.name, n, 10);
return &dev;
}
What's the potential problem in the function createDevice
?
Answer: The function returns a pointer to a local variable dev
, which will be out of scope after the function returns. This will lead to undefined behavior when accessing it.
44. Question:
struct Vector {
int* data;
size_t size;
};
struct Vector v;
int arr[] = {1, 2, 3, 4, 5};
v.data = arr;
v.size = sizeof(arr);
What's wrong in the code?
Answer: The assignment to v.size
calculates the size in bytes of the array arr
, not the number of elements. To get the correct number of elements, it should be sizeof(arr) / sizeof(int)
.
45. Question:
struct Sample {
char flag;
double value;
} __attribute__((packed));
double getValue(struct Sample s) {
return s.value;
}
What's potentially problematic in the above code on certain architectures?
Answer: The __attribute__((packed))
ensures there's no padding in the struct. But accessing the value
member directly from a packed struct may result in misaligned memory access for double
. On some architectures, this can cause a crash or reduced performance.
46. Question:
struct Params {
int* data;
size_t length;
};
void modify(struct Params p, int index) {
if (index < p.length) {
p.data[index] = 100;
}
}
int array[5] = {0};
struct Params par = {array, 5};
modify(par, 10);
What's the issue in the above code?
Answer: The function modify
doesn't check if the index is negative, and the check for index < p.length
alone isn't sufficient to prevent out-of-bounds access. The call to modify(par, 10)
would attempt to access memory out of the bounds of the array.
47. Question:
struct Measurement {
float temp;
char unit[2];
};
struct Measurement m = {23.5, "C"};
What's the potential issue here?
Answer: The unit
array has space for only 2 characters. Storing the string "C" requires 2 bytes (1 for the character 'C' and 1 for the null terminator). This could potentially lead to unintended behavior or memory corruption.
48. Question:
struct FileInfo {
char filename[20];
size_t size;
};
struct FileInfo files[10];
memset(files, 0, 10);
What's wrong in the code?
Answer: The memset
function is called with the wrong size. Instead of 10
, it should be sizeof(files)
to properly initialize the entire array.
49. Question:
struct Data {
char flag;
union {
int intValue;
char charValue;
};
};
struct Data d;
d.charValue = 'A';
d.intValue = 10;
printf("%c\n", d.charValue);
What might be confusing or problematic about the output?
Answer: Since intValue
and charValue
share the same memory location within the union, setting intValue
after charValue
will overwrite the previously set value. The output might not be 'A' and could be unexpected.
50. Question:
struct Container {
char* name;
int items[5];
};
struct Container box = {.name = "Box1", .items = {1, 2, 3, 4, 5}};
box.items = {5, 4, 3, 2, 1};
What's the compilation issue in the code?
Answer: Arrays are not modifiable lvalues, so you cannot assign to them directly. The line box.items = {5, 4, 3, 2, 1};
will result in a compilation error.