Javatpoint Logo
Javatpoint Logo

Generic Keyword in C

In this article, we will discuss the Generic Keyword in C with its syntax, functions, and examples.

The C11 standard introduced a practical feature that makes type-generic programming possible: the _Generic keyword. It makes it easy to construct generic code that can easily handle many data types by enabling conditional compilation based on the kinds of parameters supplied to a macro. It is beneficial for emulating function overloading in C because various behavior is required depending on the types of arguments.

Syntax:

The syntax of _Generic in C is:

Here is a thorough description of each component:

Expression: The expression represents type controls which code should be executed. The type of this phrase is determined, and it is compared to the given type of association.

type1, type2, ..., default: These are type associations or type labels. Each type label is followed by the code that will be run if the expression's type fits that specific type, and a colon (:). If none of the supplied types apply to the expression's type, the optional default label indicates the code to be run.

code1, code2, ..., codeN: The code blocks for each type label are code1, code2,..., codeN. If the associated type label fits the type of the expression, the statements in each code block will be performed.

The _Generic construct is often employed when you want to choose different code paths at compile time dependent on the type of the expression. Although it has no direct connection to C macros, type-generic behavior can be achieved by using it within macros.

How to use _Generic in C :

The _Generic keyword can be used wherever in our code that needs some generic code. Generics are not directly supported in C like they are in some other programming languages. However, by employing specific strategies, you can develop type-generic programming and produce functions or macros that work with various data types. Here are a few typical methods for using generics in C:

Function Pointers: Function pointers can be used to implement type-generic behavior. You can call several functions depending on the kind of data by supplying a function pointer to a generic function. Here's an illustration:

Program:

Output:

42
3.14

Explanation:

Include Header Files: The code includes the standard input-output library (stdio. h) to use printing functions like printf().

Define Function Pointer Type: The code declares a new function pointer type called PrintFunction. When a function takes a void* argument and returns void, its address is stored in this function pointer type. We can create a generic function that can accept various data types.

Generic Function Definition: Two arguments can be passed to the generic method printValue.

Void* data: It is a pointer to the data that we want to print, which is a void*. We can take a pointer to any data type using the void* type without knowing its type.

PrintFunction print: The function pointer variable known as PrintFunction print denotes the precise function used to print the data. The print function pointer belongs to the PrintFunction type that we previously established.

Specific print features: The code designates two distinct functions, printing and printFloat, to print integers and floats, respectively. These functions accept a void* parameter, but to access and print the values correctly, we cast the void* pointer to the relevant data type (for example, int* for printing and float* for printFloat).

Main Function: Two variables are declared in the main Function.

float value: A float variable with an initial value of 3.14.

intValue: An integer variable initialized to 42.

Calling the Generic method: Next, we use two different data types to call the printValue method twice.

  • The address of intValue and the function pointer printing are passed on the initial call. The printing method is called the printValue function, which outputs the integer 42.
  • The address of floatValue and the function pointer printFloat are passed during the second call. When the printValue function calls the printFloat function, the float value 3.14 is printed.

Complexity Analysis:

Time Complexity:

The provided code has constant (O(1)) time complexity. Two printValue function calls are made in the main function, which calls either printing or printFloat. The printValue function runs continuously because it just uses the function pointer to invoke the proper function. Both function calls require the same time, independent of the input's (the data's) size.

Space complexity:

The provided code has constant (O(1)) space complexity. The amount of memory used is independent of the input's size (intValue and floatValue). The primary cause is that no additional memory is allocated to store the data inside the routines; instead, the data is supplied to the functions as pointers (void*). No matter the input size, function calls and pointers always require the same amount of memory.

Generics in Macros:

Macros are preprocessor directives in C that let you define essential functions directly within the code. Macros functions allow programmers to replace because it just uses the function pointer to invoke the proper runction, with predefined expressions or lines of code that are expanded at compile time. Producing type-specific code based on the macro inputs offers a method to define generic behavior.

Syntax:

A function macro has the following syntax:

MACRO_NAME: The MACRO_Name represents the name that you use to invoke the macro.

Arguments: The list of parameters that the macro accepts, separated by commas. These are similar to function parameters.

Generic macros for Arithmetic Operations: The code or expression that the macro expands to when it is called replacement_expression. It may also contain other code and macro arguments.

Program:

Output:

Result of integer addition: 15
Result of float addition: 5.85
Result of double addition: 2.195

Explanation:

  • In this example, we define a macro function called ADD. The values to be added are represented by the two arguments, x, and y, that this macro accepts.
  • We use the _Generic keyword inside the ADD macro to choose the proper function based on the data type of the (x) + (y) expressions. The type for the generic selection is determined using the phrase (x) + (y).
  • The _Generic keyword converts the type of the expression (x) + (y) to the add_int, add_float, or add_double type-specific functions.
  • After that, we offer three functions that handle addition for their respective data types: add_int, add_float, and add_double.
  • We declare the variables intResult, floatResult, and doubleResult in the main function to hold the outcomes of the addition.
  • The ADD macro executes addition for various data types, and the results are then saved in the appropriate variables.
  • After that, we print the outcomes for each data type.

Complexity Analysis:

The provided C code, which implements addition for several data types via function macros, has the following time and space complexity:

Time Complexity:

The code has an O(1) time complexity. Using the ADD macro, we do the following three addition operations in the main function:

intResult = ADD(a, b);

floatResult = ADD(x, y);

doubleResult = ADD(m, n);

Depending on the sort, one of the add_int, add_float, or add_double methods is used for every addition operation. The time needed for every addition operation is regular, no matter the values for a, b, x, y, m, and n. The time complexity remains regular regardless of the number of operations or the volume of entering statistics.

Space Complexity:

The space complexity of the code is also constant (O(1)). The amount of memory used at some stage in the application is consistent regardless of the size of the input facts. It allows memory for function parameters and variables (intResult, floatResult, doubleResult, a, b, x, y, m, n) which can be unbiased of the dimensions of the enter. Recursive characteristic calls and dynamically allotted facts systems that can boost space complexity are absent.

Using _Generic (C11 and later):

The _Generic keyword enables you to conditionally build code based on the data type of an expression, as was covered in earlier responses. This ability makes type-generic programming viable.

Program:

Output:

Square of 5: 25
Square of 3.14: 9.86
Square of 2.718: 7.388

Explanation:

Using the _Generic keyword in this code, the square macro chooses the proper expression based on the type of the argument x. The square of an integer, a float, or a double can be easily calculated with the macro. The expression (x) * (x) is used when the macro is used with an integer argument, which squares the integer. Similarly, when called with a float or double parameter, it computes the square of those data types using the appropriate formulas. We may handle many data types by utilizing a single macro due to the _Generic feature's type-generic behavior.

  1. Include Header File: The code includes the standard input-output library (stdio. h) to utilize functions like printf.
  2. Define Function Macro: The code creates a square-themed function macro. The number we wish to square is represented by the input x, which is the only argument the macro accepts.
  3. Making use of _Generic: The square macro makes use of the _Generic keyword. Conditional compilation based on the type of the expression (x in this case) is a potent feature made available in the C11 standard.
  4. Type Associations: Using colons (:), the code provides various type associations for the x expression within the _Generic keyword:
    • The corresponding statement for the int type, (x) * (x), denotes the computation of an integer's square.
    • The corresponding statement for the float type, (x) * (x), denotes the calculation of a float's square.
    • The corresponding expression for the square root of a double for the double type is (x) * (x).
  5. Main Function: In the main function, 3 variables are defined and initialized: The values of the three variables are five for the integer "intValue", three., 14 for the waft "floatValue", and a double of 718 for the double "doubleValue".
  6. Calling the Macro: Each variable's square is computed using the square macro, and the results are printed using the printf() function.
  7. Output: For integers, floats, and doubles, the code outputs the square of each variable in a specified format.

Complexity Analysis:

Time Complexity:

The code has an O(1) time complexity. Three calls to the printf() function, which is independent of the input data size and is constant, are made in the main function. A simple mathematical operation like multiplication is likewise included in the square macro, and it requires a consistent amount of time regardless of the input. All operations in the code execute in a fixed amount of time, regardless of the size of the supplied data.

Space Complexity:

The code's space complexity is similarly fixed (O(1)). The program's execution always uses the same amount of memory. Variables with fixed memory allotments that are independent of the size of the input data are utilized in the main function's memory, such as intValue, floatValue, and doubleValue. A fixed amount of memory is also used by the printf() function regardless of the input data.

The memory utilized by the square macro is small and constant, and no additional memory is allocated. It executes the mathematical operations directly without adding a lot of memory overhead.

Unions:

In C, unions allow storing various data in the same memory address. Unions can offer a mechanism to handle numerous data types in specific circumstances, even though they are not quite generics.

Program:

Output:

Integer: 42
Float: 3.14
Double: 2.718

Explanation:

Include Header File: The code includes the standard input-output library (stdio. h) to utilize the printf() function for printing.

Enumeration for Data kinds: An enumeration called DataType is defined to represent various data types. It specifies three constants, INT, FLOAT, and DOUBLE, representing the data types double, float, and integer.

Generic Data Holder using a Union: GenericData is the name of the defined struct. It serves as an all-purpose data holder for storing data of different kinds. It includes two fields:

  • DataType type: An enumeration feature used to specify the kinds of data that a structure can hold.
  • Union: A union based on the type field that can store various data kinds. It can store a floating-point number (float), an integer (int), or a double-precision floating-point numbers number (double).

Print Values Based on Data Type function: Create a printValue function that accepts a GenericData structure as an input is possible. Using a switch statement, the function examines the type field of the GenericData structure and prints the value kept in the relevant data field based on the data type.

Main Function: Three GenericData variables (data1, data2, and data3) are declared and initialized with particular values and data types in the main function.

Calling the Function: Three calls to the printValue function are made, passing each of the GenericData variables as arguments. The function prints the values in each GenericData structure according to their data types.

Output: Based on each data type, the algorithm outputs the values of data1, data2, and data3.

Complexity Analysis:

Time Complexity:

The code has an O(1) time complexity. The point value function is called three times by the code, independent of the input data size and constant. A switch statement based on the DataType enumeration inside the printValue method also runs in constant time. As a result, each call to printValue and the switch statement take the same amount of time, and the time complexity is independent of the input size.

Space Complexity:

The code's space complexity is constant (O(1)). The program's execution uses the same amount of memory at all times. Three GenericData variables (data1, data2, and data3) are declared and initialized in the main function. These variables' memory requirements are constant and independent of the amount of input data.

The printValue method uses very little and constant memory. The function solely uses memory for its GenericData parameter, whose size is unaffected by the input size.

Additionally, the printValue function's switch statement chooses the proper case depending on the data type value rather than allocating more RAM.

Conclusion:

Writing generic C code requires creative and careful analysis of the best course of action based on the unique use case. The choice will be based on the complexity of the code, type of safety requirements, and maintainability. Each solution presented has advantages and disadvantages. In some circumstances, macros might be sufficient for straightforward tasks. However, function pointers and the use of _Generic are preferable for more complex situations and improved type safety.

When creating generic behavior in C, developers should always aim for clean, maintainable, and error-free code. To write reliable and effective type-generic code, it is essential to comprehend each approach's drawbacks and potential hazards.







Youtube For Videos Join Our Youtube Channel: Join Now

Feedback


Help Others, Please Share

facebook twitter pinterest

Learn Latest Tutorials


Preparation


Trending Technologies


B.Tech / MCA