Skip to content

C Typedef

The typedef keyword in C provides a way to create an alias or a synonym for an existing data type. It doesn’t create a new type, but rather gives another name to a type that is already defined.

This is particularly useful for:

  • Improving Readability: Using meaningful names for complex types.
  • Simplifying Declarations: Making declarations of complex types (like pointers to structures or function pointers) easier to write and understand.
  • Portability: Defining custom types (like BYTE or INT32) that can be adjusted for different platforms in one place.

The basic syntax follows the pattern of a variable declaration, but with the typedef keyword prepended:

typedef existing_type new_type_name;

existing_type is the data type you want to create an alias for (e.g., unsigned char, int *, struct ComplexNumber). new_type_name is the alias (synonym) you want to define.

#include <stdint.h> // For int32_t
// Create aliases for basic or standard types
typedef unsigned char BYTE;
typedef int32_t INTEGER32;
typedef char* STRING; // Alias for pointer to char
int main(void) {
BYTE status_flag = 0x01; // status_flag is actually an unsigned char
INTEGER32 counter = -50000; // counter is actually an int32_t
STRING message = "Hello typedef!"; // message is actually a char*
printf("Status: %u\n", status_flag);
printf("Counter: %d\n", counter);
printf("Message: %s\n", message);
return 0;
}

By convention, typedef names are often uppercase (like BYTE), but this is not a strict rule.

Simplifies declaring structure variables by removing the need to write struct TagName repeatedly.

// Original structure definition
struct Point2D {
double x;
double y;
};
// Typedef for the structure
typedef struct Point2D Coordinate;
// Or combine definition and typedef:
typedef struct Vector3D {
double x, y, z;
} Vector; // Vector is now an alias for struct Vector3D
int main(void) {
struct Point2D p1; // Using original struct name
Coordinate p2; // Using the typedef alias - cleaner!
Vector v1; // Using the combined typedef alias
p2.x = 1.0;
p2.y = 2.0;
v1 = (Vector){3.0, 4.0, 5.0}; // Compound literal with typedef
printf("Point p2: (%.1f, %.1f)\n", p2.x, p2.y);
printf("Vector v1: (%.1f, %.1f, %.1f)\n", v1.x, v1.y, v1.z);
return 0;
}

Can make pointer declarations less cluttered.

typedef int* IntPtr; // IntPtr is an alias for 'int *'
typedef void (*CallbackFunc)(int); // CallbackFunc is an alias for a pointer
// to a function that takes an int and returns void
void my_callback(int id) {
printf("Callback called with ID: %d\n", id);
}
int main(void) {
int value = 10;
IntPtr ptr1 = &value; // ptr1 is an int*
IntPtr ptr2 = NULL; // ptr2 is an int*
CallbackFunc func_ptr = my_callback; // Assign function address
*ptr1 = 20;
printf("Value via ptr1: %d\n", *ptr1);
if (func_ptr != NULL) {
func_ptr(123); // Call the function via the pointer alias
}
return 0;
}

While both can create aliases, they work differently:

  • typedef: Handled by the compiler. It understands C syntax and creates true type synonyms. It respects scope.
  • #define: Handled by the preprocessor (before compilation). It performs simple text substitution, unaware of C types or scope. Can lead to unexpected behavior, especially with pointers or complex types.

Example difference with pointers:

#define StringMacro char*
typedef char* StringTypedef;
int main(void) {
StringMacro p1, p2; // Preprocessor expands to: char* p1, p2;
// Here, p1 is char*, but p2 is just char!
StringTypedef pt1, pt2; // Typedef applies to both:
// pt1 is char*, and pt2 is char*
return 0;
}

Best Practice: Use typedef for creating type aliases as it’s safer and better integrated with the C language’s type system.

Input refers to feeding data into a program, typically from the keyboard or a file. Output refers to displaying data from the program, usually to the screen or a file.

C provides standard library functions, primarily declared in <stdio.h> (Standard Input/Output), to handle these operations.

When a C program starts, three standard communication channels (streams) are automatically opened:

Stream NameFile PointerDefault Device
Standard InputstdinKeyboard (by default)
Standard OutputstdoutScreen/Console (by default)
Standard ErrorstderrScreen/Console (by default)

stdin, stdout, and stderr are predefined FILE* pointers (file pointers) that allow access to these streams. Standard output is typically buffered (output may not appear immediately), while standard error is usually unbuffered (error messages appear promptly).

Character Input/Output: getchar() & putchar()

Section titled “Character Input/Output: getchar() & putchar()”
  • int getchar(void): Reads the next available character from stdin and returns it as an int. Returns EOF (a special integer constant indicating End-Of-File) on error or end of input.
  • int putchar(int c): Writes the character c (passed as an int) to stdout and returns the character written, or EOF on error.

These functions handle one character at a time.

Example:

#include <stdio.h>
int main(void) {
int input_char;
printf("Enter a character: ");
input_char = getchar(); // Read one character
if (input_char == EOF) {
printf("\nError or end of input detected.\n");
} else {
printf("\nYou entered: ");
putchar(input_char); // Print the character
putchar('\n');
}
// Note: getchar() often reads the newline ('\n') too if you press Enter.
// You might need extra getchar() calls to consume leftover input.
return 0;
}

Execution:

$ ./a.out
Enter a character: H
You entered: H
  • char *fgets(char *str, int size, FILE *stream): Reads a line of text from the specified stream (e.g., stdin) into the buffer str. It stops reading when size - 1 characters are read, a newline character (\n) is encountered, or EOF occurs. Crucially, fgets includes the newline character in the buffer if it fits, and it always null-terminates the buffer (if size > 0). Returns str on success, NULL on error or EOF before reading any characters. This is the recommended safe way to read strings.
  • int puts(const char *str): Writes the string str to stdout, followed by a newline character (\n). Returns a non-negative value on success, or EOF on error.

Warning: The older gets(char *s) function is extremely dangerous because it doesn’t check buffer sizes, leading easily to buffer overflows. It has been removed from recent C standards. Never use gets(). Always use fgets().

Example using fgets:

#include <stdio.h>
#include <string.h> // For strcspn
#define BUFFER_SIZE 100
int main(void) {
char input_line[BUFFER_SIZE];
printf("Enter a line of text: ");
if (fgets(input_line, sizeof(input_line), stdin) != NULL) {
// Optional: Remove trailing newline if present
input_line[strcspn(input_line, "\n")] = '\0';
printf("\nYou entered: ");
puts(input_line); // puts adds its own newline
} else {
printf("\nError reading input or EOF reached.\n");
}
return 0;
}

Execution:

$ ./a.out
Enter a line of text: This is a test line.
You entered: This is a test line.

Formatted Input/Output: scanf() & printf()

Section titled “Formatted Input/Output: scanf() & printf()”
  • int printf(const char *format, ...): Writes formatted output to stdout. The format string contains text to be printed and format specifiers (starting with %) that indicate how subsequent arguments should be converted and printed (e.g., %d for int, %f for float/double, %c for char, %s for string, %p for pointer). Returns the number of characters printed or a negative value on error.
  • int scanf(const char *format, ...): Reads formatted input from stdin. The format string specifies the expected format of the input using format specifiers. Subsequent arguments must be pointers to variables where the read data will be stored. Returns the number of input items successfully matched and assigned, or EOF on input failure before any conversion.

Common Format Specifiers:

  • %d or %i: Signed integer
  • %u: Unsigned integer
  • %f: Floating-point number (float/double for printf, float* for scanf)
  • %lf: Long float (double* for scanf)
  • %Lf: Long double
  • %c: Single character
  • %s: String (reads up to whitespace for scanf)
  • %p: Pointer address
  • %%: Literal ’%’ character

Example:

#include <stdio.h>
int main(void) {
char name[50];
int age;
float height;
printf("Enter your name, age, and height (e.g., Alice 30 1.65): ");
// Use width limits with %s in scanf to prevent buffer overflow!
int items_read = scanf("%49s %d %f", name, &age, &height);
if (items_read == 3) { // Check if all 3 items were read successfully
printf("\n--- User Info ---\n");
printf("Name : %s\n", name);
printf("Age : %d years\n", age);
printf("Height : %.2f meters\n", height); // Print float with 2 decimal places
} else {
printf("\nError: Invalid input format.\n");
// Clear remaining invalid input from buffer (simple example)
while (getchar() != '\n');
}
return 0;
}

Execution:

$ ./a.out
Enter your name, age, and height (e.g., Alice 30 1.65): Bob 25 1.8
--- User Info ---
Name : Bob
Age : 25 years
Height : 1.80 meters

scanf Pitfalls:

  • Buffer Overflows: Using %s without a width limit (%49s) is dangerous.
  • Input Format Matching: Input must exactly match the format string (including whitespace).
  • Whitespace Handling: %s skips leading whitespace and stops at the next whitespace. %c reads the next character, including whitespace.
  • Newline Character: scanf often leaves the newline character (\n) from the Enter key press in the input buffer, which can interfere with subsequent fgets or getchar calls.
  • Error Checking: Always check the return value of scanf to ensure the expected number of items were successfully read.

Due to these issues, fgets followed by parsing functions like sscanf or strtol is often a more robust way to handle user input.