C ఫైల్ హ్యాండ్లింగ్ (File Handling): టెక్స్ట్ & బైనరీ ఫైల్స్, స్ట్రీమ్స్ & పర్సిస్టెన్స్

⚡ Modern C (C99 / C11 / C17 Standards) 🟢 Chapter 19 of 20 📂 Phase 05: స్ట్రక్చర్స్, డైనమిక్ మెమరీ & ప్రాజెక్ట్ 📅 2026 Academic Edition
📌 Covered in this chapter: డేటా పర్సిస్టెన్స్ ఆర్కిటెక్చర్ · FILE* పాయింటర్ · fopen() మోడ్స్ ("r", "w", "a", "rb", "wb") · NULL చెకింగ్ & perror() · fgetc/fputc · fgets/fputs · fprintf/fscanf · టెక్స్ట్ vs బైనరీ · fread/fwrite · fseek/ftell/rewind రాండమ్ యాక్సెస్ · fflush & feof గాట్చాస్ · పర్సిస్టెంట్ ఫోన్‌బుక్ ప్రాజెక్ట్

హలో C ప్రోగ్రామర్! 👋 డేటాను శాశ్వతంగా దాచుకునే సూపర్ పవర్ నేర్చుకుందాం!

ఇప్పటివరకు మనం రాసిన అన్ని C ప్రోగ్రామ్‌లలో ఒక పెద్ద లోపం ఉంది: ప్రోగ్రామ్ రన్ అవుతున్నంత సేపు మాత్రమే డేటా RAM లో జీవించి ఉంటుంది. టెర్మినల్ క్లోజ్ చేయగానే లేదా కంప్యూటర్ రీస్టార్ట్ చేయగానే వేరియబుల్స్, అర్రేలు, స్ట్రక్చర్లు అన్నీ ఆవిరైపోతాయి! దీనిని కంప్యూటర్ సైన్స్‌లో వోలటైల్ మెమరీ (Volatile Memory) అంటారు.

నిజమైన సాఫ్ట్‌వేర్ ఉత్పత్తులు—బ్యాంకింగ్ అప్లికేషన్లు, గేమ్‌లలో సేవ్ ఫైల్స్, యూజర్ ప్రొఫైల్స్, సర్వర్ లాగ్స్—డేటాను ఎప్పటికీ కోల్పోకూడదు. హార్డ్ డిస్క్ లేదా SSD లో డేటాను శాశ్వతంగా భద్రపరచడాన్నే డేటా పర్సిస్టెన్స్ (Data Persistence) అంటారు. C భాషలో <stdio.h> లైబ్రరీ అందించే ఫైల్ హ్యాండ్లింగ్ (File Handling) ద్వారా ఫైల్స్ సృష్టించడం, టెక్స్ట్ మరియు బైనరీ ఫార్మాట్లలో డేటాను రాయడం, చదవడం, మరియు మధ్యలోని రికార్డులను డైరెక్ట్‌గా మోడిఫై చేయడం ఎలాగో ఈ 19వ అధ్యాయంలో సమగ్రంగా నేర్చుకుందాం!

1 ఫైల్ హ్యాండ్లింగ్ ప్రాముఖ్యత: పర్సిస్టెన్స్ & స్ట్రీమ్స్ ఆర్కిటెక్చర్

కంప్యూటర్ మెమరీ ఆర్కిటెక్చర్‌లో RAM (ప్రైమరీ మెమరీ) వేగవంతమైనది కానీ తాత్కాలికమైనది. హార్డ్ డ్రైవ్ / SSD (సెకండరీ స్టోరేజ్) నిదానమైనది కానీ శాశ్వతమైనది (Non-Volatile). C ప్రోగ్రామ్‌లు నేరుగా హార్డ్ డిస్క్‌లోని సెక్టార్లతో మాట్లాడలేవు; అవి ఆపరేటింగ్ సిస్టమ్ ద్వారా స్ట్రీమ్స్ (Streams) అనే భావనతో పనిచేస్తాయి.

స్ట్రీమ్ రకం (Stream) స్టాండర్డ్ పాయింటర్ కనెక్ట్ అయ్యే పరికరం ఉపయోగం
Standard Input stdin కీబోర్డ్ (Keyboard) యూజర్ నుండి డేటాను రీడ్ చేయడం (scanf, getchar).
Standard Output stdout మానిటర్ / కన్సోల్ స్క్రీన్ ఔట్‌పుట్‌ను ప్రింట్ చేయడం (printf, putchar).
Standard Error stderr మానిటర్ / ఎర్రర్ లాగ్ బఫరింగ్ లేకుండా తక్షణమే ఎర్రర్లను డిస్‌ప్లే చేయడం.
Custom File Stream FILE *fp హార్డ్ డిస్క్ లోని ఫైల్ శాశ్వత ఫైల్స్ నుండి డేటాను చదవడం / రాయడం.

స్ట్రీమ్ కమ్యూనికేషన్ ఫ్లో డయాగ్రమ్:

[ C Program / RAM Variables ]
│
▼ (fprintf / fputs / fwrite)
[ C Standard I/O Buffer in RAM ] <--- వేగం కోసం C లైబ్రరీ నిర్వహించే తాత్కాలిక బఫర్
│
▼ (fflush / fclose / Buffer Full)
[ Operating System Kernel Cache ]
│
▼ (Physical Disk Write)
[ Hard Disk / SSD (Persistent Storage) ]

2 FILE* పాయింటర్ మరియు <stdio.h> బేసిక్స్

C భాషలో ఫైల్స్‌తో పనిచేసే ప్రతి ఆపరేషన్ FILE* అనే ప్రత్యేక పాయింటర్ ద్వారా జరుగుతుంది. FILE అనేది ఒక స్ట్రక్చర్, ఇది <stdio.h> లో నిర్వచించబడింది. ఇందులో:

  • ఫైల్ డిస్క్రిప్టర్ నంబర్ (OS హ్యాండిల్)
  • ప్రస్తుత ఫైల్ కర్సర్ లొకేషన్ (రీడ్/రైట్ పాయింటర్)
  • ఇంటర్నల్ మెమరీ బఫర్ అడ్రస్
  • ఎర్రర్ మరియు ఎండ్-ఆఫ్-ఫైల్ (EOF) ఫ్లాగ్స్ ఉంటాయి.

డెవలపర్‌గా మనం ఆ స్ట్రక్చర్ లోపలి వేరియబుల్స్‌ను నేరుగా తాకాల్సిన పనిలేదు. ఆపరేటింగ్ సిస్టమ్ ఆ స్ట్రక్చర్‌ను హీప్‌లో మెయింటైన్ చేస్తుంది, మనకు కేవలం దాని చిరునామాను FILE* గా ఇస్తుంది.

ఫైల్ లైఫ్‌సైకిల్ (3 గోల్డెన్ స్టెప్స్):

  1. Open File: fopen() ద్వారా ఫైల్ స్ట్రీమ్‌ను తెరవడం.
  2. Process File: డేటాను రాయడం (Write) లేదా చదవడం (Read).
  3. Close File: fclose() ద్వారా బఫర్లను ఖాళీ చేసి ఫైల్‌ను క్లోజ్ చేయడం.

3 fopen() ఫైల్ ఓపెనింగ్ మోడ్‌లు: సమగ్ర పోలిక పట్టిక

ఫైల్‌ను తెరవడానికి fopen() ఫంక్షన్‌ను వాడతాం:

FILE *fopen(const char *filename, const char *mode);

ఇక్కడ mode చాలా కీలకమైనది. మనం ఫైల్‌ను చదవడానికి తెరుస్తున్నామా, రాయడానికి తెరుస్తున్నామా లేదా చివరన అదనపు డేటాను జోడించడానికి తెరుస్తున్నామా అనేది ఈ మోడ్ నిర్ణయిస్తుంది:

మోడ్ (Mode) ఉద్దేశం (Purpose) ఫైల్ ఇప్పటికే ఉంటే? ఫైల్ లేకపోతే? కర్సర్ ప్రారంభ స్థానం
"r" చదవడానికి మాత్రమే (Read Only) ఫైల్ తెరవబడుతుంది. వైఫల్యం (NULL రిటర్న్ అవుతుంది) ప్రారంభంలో (Beginning)
"w" రాయడానికి (Write Only) పాత డేటా మొత్తం తుడిచిపెట్టుకుపోతుంది! (Overwritten) కొత్త ఫైల్ సృష్టించబడుతుంది. ప్రారంభంలో (0 బైట్)
"a" చివరన జోడించడానికి (Append) పాత డేటా సురక్షితంగా ఉంటుంది. కొత్త ఫైల్ సృష్టించబడుతుంది. ఫైల్ చివరన (End of File)
"r+" రీడ్ & రైట్ (Read & Write) ఫైల్ తెరవబడుతుంది, డేటా తుడవబడదు. వైఫల్యం (NULL రిటర్న్) ప్రారంభంలో
"w+" రీడ్ & రైట్ (కొత్తగా సృష్టి) పాత డేటా మొత్తం తుడిచివేయబడుతుంది! కొత్త ఫైల్ సృష్టించబడుతుంది. ప్రారంభంలో
"a+" రీడ్ & అపెండ్ (Read & Append) పాత డేటా అలాగే ఉంటుంది. కొత్త ఫైల్ సృష్టించబడుతుంది. రాయడం ఎల్లప్పుడూ చివరనే జరుగుతుంది.
🚨 "w" మోడ్ ప్రమాదం:
ముఖ్యమైన డేటా ఉన్న ఫైల్‌ను పొరపాటున "w" మోడ్‌లో ఓపెన్ చేస్తే, OS ఆ ఫైల్‌లోని ప్రతి అక్షరాన్ని వెనువెంటనే తుడిచివేస్తుంది (Truncate to 0 bytes)! డేటాను కోల్పోకుండా కేవలం కొత్త అంశాలను కలపాలంటే ఎల్లప్పుడూ "a" (append) మోడ్ వాడాలి!

4 ఫైల్ ఓపెనింగ్ వైఫల్యాలు & NULL చెకింగ్ (Defensive Programming)

ఫైల్ సిస్టమ్ ఆపరేషన్లలో అనేక అనిశ్చితులు ఉంటాయి. ఉదాహరణకు:

  • మనం చదవాలనుకున్న ఫైల్ ఆ ఫోల్డర్‌లో లేకపోవచ్చు (File Not Found).
  • ఫైల్ రీడ్-ఓన్లీగా ఉండవచ్చు లేదా మనకు పర్మిషన్లు లేకపోవచ్చు (Permission Denied).
  • హార్డ్ డిస్క్ నిండిపోయి ఉండవచ్చు (Disk Full).

ఇలాంటి సందర్భాలలో fopen() విఫలమై NULL ను రిటర్న్ చేస్తుంది. ఒకవేళ మీరు NULL ని చెక్ చేయకుండా ఫైల్ ఆపరేషన్ చేస్తే ప్రోగ్రామ్ తక్షణమే క్రాష్ అవుతుంది!

perror() మరియు errno తో ఎర్రర్ హ్యాండ్లింగ్:

C — File Open Error Handling with perror()
#include <stdio.h>
#include <stdlib.h>

int main() {
    // ఉనికిలో లేని ఫైల్‌ను చదవడానికి ప్రయత్నించడం
    FILE *fp = fopen("non_existent_file.txt", "r");

    if (fp == NULL) {
        // perror() సిస్టమ్ ఇచ్చిన ఖచ్చితమైన కారణాన్ని ప్రింట్ చేస్తుంది (ఉదా: No such file or directory)
        perror("ఫైల్ ఓపెన్ చేయడంలో లోపం");
        printf("సూచన: ఫైల్ పేరు మరియు పాత్ సరిగ్గా ఉన్నాయో లేదో సరిచూసుకోండి.\n");
        return EXIT_FAILURE;
    }

    printf("ఫైల్ విజయవంతంగా తెరవబడింది!\n");
    fclose(fp);
    return EXIT_SUCCESS;
}

5 క్యారెక్టర్ I/O: fgetc() మరియు fputc() తో ఫైల్ కాపీ ప్రోగ్రామ్

ఫైల్ నుండి ఒక్కో క్యారెక్టర్‌ను చదవడానికి మరియు రాయడానికి రెండు ప్రాథమిక ఫంక్షన్లు ఉన్నాయి:

  • int fputc(int char, FILE *stream); — ఒక అక్షరాన్ని ఫైల్‌లో రాస్తుంది.
  • int fgetc(FILE *stream); — ఫైల్ నుండి తదుపరి అక్షరాన్ని చదువుతుంది.
❓ fgetc() చార్ కాకుండా int ని ఎందుకు రిటర్న్ చేస్తుంది?
ఇది ప్రతి ఇంటర్వ్యూలో అడిగే ఫేవరెట్ ప్రశ్న! ఒక సాధారణ char 0 నుండి 255 (లేదా -128 నుండి 127) వరకు మాత్రమే స్టోర్ చేయగలదు. కానీ ఫైల్ చివరను సూచించే EOF (End of File) విలువ సాధారణంగా -1 గా డిఫైన్ చేయబడుతుంది. అన్ని 256 బైనరీ క్యారెక్టర్లను మరియు అదనంగా ప్రత్యేక EOF విలువను స్పష్టంగా వేరు చేయడానికి fgetc() int ను రిటర్న్ చేస్తుంది! అందుకే చదివే వేరియబుల్‌ను ఎల్లప్పుడూ int ch; గానే డిక్లేర్ చేయాలి.

ప్రాక్టికల్ ప్రాజెక్ట్: ఫైల్ కాపీ మరియు క్యారెక్టర్ & లైన్ కౌంటర్

C — Character I/O (fgetc & fputc) Copy Program
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 1. మొదట ఒక డెమో ఫైల్ సృష్టిద్దాం
    FILE *source = fopen("notes.txt", "w");
    if (source == NULL) {
        perror("సోర్స్ ఫైల్ క్రియేషన్ విఫలమైంది");
        return 1;
    }
    fputs("నమస్కారం! ఇది C ఫైల్ హ్యాండ్లింగ్ డెమో.\nలైన్ నంబర్ 2: C చాలా శక్తివంతమైనది.\nలైన్ నంబర్ 3: హ్యాపీ కోడింగ్!", source);
    fclose(source);

    // 2. ఇప్పుడు ఆ ఫైల్‌ను చదివి మరొక ఫైల్‌లోకి కాపీ చేయడం
    FILE *src = fopen("notes.txt", "r");
    FILE *dest = fopen("notes_backup.txt", "w");

    if (src == NULL || dest == NULL) {
        perror("ఫైల్ ఓపెన్ ఎర్రర్");
        if (src) fclose(src);
        if (dest) fclose(dest);
        return 1;
    }

    int ch;
    long char_count = 0;
    long line_count = 0;

    // ఫైల్ ముగిసే వరకు (EOF) ఒక్కో అక్షరాన్ని చదవడం
    while ((ch = fgetc(src)) != EOF) {
        fputc(ch, dest); // డెస్టినేషన్ ఫైల్‌లో రాయడం
        char_count++;
        if (ch == '\n') {
            line_count++;
        }
    }
    // చివరి లైన్ కి న్యూలైన్ లేకపోతే కౌంట్ సరిచేయడం
    if (char_count > 0) line_count++;

    fclose(src);
    fclose(dest);

    printf("✅ ఫైల్ విజయవంతంగా కాపీ చేయబడింది!\n");
    printf("మొత్తం అక్షరాలు: %ld\n", char_count);
    printf("మొత్తం లైన్లు: %ld\n", line_count);

    return 0;
}

6 లైన్ I/O: fgets() మరియు fputs() తో సురక్షితమైన స్ట్రింగ్ రీడింగ్

ఒక్కో అక్షరం కాకుండా మొత్తం ఒక వాక్యాన్ని లేదా లైన్‌ను చదవడానికి మరియు రాయడానికి స్ట్రింగ్ ఫంక్షన్లు వాడతాం:

  • int fputs(const char *str, FILE *stream); — స్ట్రింగ్‌ను ఫైల్‌లో రాస్తుంది.
  • char* fgets(char *str, int num, FILE *stream); — ఫైల్ నుండి గరిష్టంగా num - 1 అక్షరాల వరకు ఒక లైన్‌ను చదువుతుంది.
🛡️ బఫర్ ఓవర్‌ఫ్లో రక్షణ:
C లో పాత gets() ఫంక్షన్ ఎంత ప్రమాదకరమో మనం స్ట్రింగ్స్ చాప్టర్‌లో చూశాం. fgets() లో మనం బఫర్ సైజును ఖచ్చితంగా పేర్కొంటాం కాబట్టి అది ఎప్పటికీ మెమరీ పరిమితిని దాటదు. ఇది న్యూలైన్ '\n' క్యారెక్టర్‌ను కూడా బఫర్‌లో ఉంచుతుంది మరియు చివరన '\0' ను జోడిస్తుంది.

fgets() తో టెక్స్ట్ ఫైల్‌ను లైన్ బై లైన్ రీడ్ చేసే కోడ్:

C — Safe Line Reading using fgets()
#include <stdio.h>
#include <stdlib.h>

#define BUFFER_SIZE 256

int main() {
    FILE *fp = fopen("config.txt", "w");
    if (!fp) return 1;

    fputs("SERVER_PORT=8080\n", fp);
    fputs("DATABASE_URL=localhost:5432\n", fp);
    fputs("DEBUG_MODE=true\n", fp);
    fclose(fp);

    // చదవడానికి తెరవడం
    fp = fopen("config.txt", "r");
    if (!fp) return 1;

    char line[BUFFER_SIZE];
    int line_num = 1;

    printf("--- కాన్ఫిగరేషన్ ఫైల్ కంటెంట్ ---\n");
    // fgets ఫైల్ చివరకు రాగానే NULL రిటర్న్ చేస్తుంది
    while (fgets(line, sizeof(line), fp) != NULL) {
        printf("[%02d]: %s", line_num++, line);
    }

    fclose(fp);
    return 0;
}

7 ఫార్మాటెడ్ I/O: fprintf() మరియు fscanf()

మనకు సాధారణ printf() మరియు scanf() ఎలాగో, ఫైల్స్‌తో మాట్లాడటానికి fprintf() మరియు fscanf() అలాగే పనిచేస్తాయి. కేవలం మొదటి ఆర్గ్యుమెంట్‌గా FILE* పాయింటర్‌ను ఇస్తే సరిపోతుంది!

int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);

fprintf మరియు fscanf తో స్టూడెంట్ గ్రేడ్ షీట్ జనరేషన్:

C — Formatted I/O (fprintf & fscanf)
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 1. డేటాను ఫార్మాట్ చేసి రాయడం (fprintf)
    FILE *fp = fopen("marks.txt", "w");
    if (!fp) { perror("ఫైల్ ఓపెన్ విఫలం"); return 1; }

    fprintf(fp, "%d %s %.2f\n", 101, "కార్తీక్", 88.5);
    fprintf(fp, "%d %s %.2f\n", 102, "లావణ్య", 94.0);
    fprintf(fp, "%d %s %.2f\n", 103, "ప్రదీప్", 76.5);
    fclose(fp);

    // 2. ఫైల్ నుండి ఫార్మాటెడ్ డేటాను చదవడం (fscanf)
    fp = fopen("marks.txt", "r");
    if (!fp) { perror("ఫైల్ రీడ్ విఫలం"); return 1; }

    int roll;
    char name[50];
    float marks;

    printf("%-8s %-15s %-8s\n", "రోల్ నం", "పేరు", "మార్కులు");
    printf("------------------------------------\n");

    // fscanf సక్సెస్ ఫుల్‌గా రీడ్ చేసిన వేరియబుల్స్ సంఖ్యను రిటర్న్ చేస్తుంది (ఇక్కడ 3)
    while (fscanf(fp, "%d %49s %f", &roll, name, &marks) == 3) {
        printf("%-8d %-15s %-8.2f\n", roll, name, marks);
    }

    fclose(fp);
    return 0;
}

8 టెక్స్ట్ ఫైల్స్ vs బైనరీ ఫైల్స్: లోతైన ఆర్కిటెక్చర్ తేడాలు

కంప్యూటర్‌లో ఫైల్స్ ప్రధానంగా రెండు వర్గాలుగా ఉంటాయి: టెక్స్ట్ ఫైల్స్ (Text Files) మరియు బైనరీ ఫైల్స్ (Binary Files). వీటి మధ్య వ్యత్యాసం అర్థం చేసుకోవడం ద్వారా సిస్టమ్ లెవల్ ప్రోగ్రామింగ్‌లో తప్పులు జరగకుండా నివారించవచ్చు:

ఫీచర్ / అంశం టెక్స్ట్ ఫైల్స్ (Text Files) బైనరీ ఫైల్స్ (Binary Files)
డేటా నిల్వ రూపం ASCII లేదా UTF-8 క్యారెక్టర్లుగా నిల్వ అవుతుంది (మానవులు చదవగలరు). RAM లోని ఖచ్చితమైన 0లు మరియు 1ల బైనరీ బైట్ల రూపంలో నేరుగా నిల్వ అవుతుంది.
విండోస్ న్యూలైన్ ట్రాన్స్‌లేషన్ \n రాస్తే అంతర్గతంగా \r\n (CRLF) గా మారుతుంది. ఎలాంటి మార్పు ఉండదు; ప్రతి బైట్ యథాతథంగా స్టోర్ అవుతుంది.
ఫైల్ సైజ్ & స్పేస్ సంఖ్యలను అక్షరాలుగా రాయడం వల్ల ఎక్కువ సైజ్ తీసుకుంటుంది (ఉదా: 12345678 కు 8 బైట్లు). చాలా తక్కువ స్థలం తీసుకుంటుంది (ఉదా: int సంఖ్యకు కేవలం 4 బైట్లే).
ప్రాసెసింగ్ వేగం నెమ్మదిగా ఉంటుంది (టెక్స్ట్ నుండి నంబర్లకు కన్వర్షన్ ఓవర్‌హెడ్ ఉంటుంది). మెరుపు వేగం (Lightning Fast): RAM మరియు డిస్క్ మధ్య డైరెక్ట్ కాపీ!
ఓపెనింగ్ మోడ్స్ "r", "w", "a" "rb", "wb", "ab" (b అంటే బైనరీ)

9 బైనరీ I/O: fwrite() మరియు fread() తో స్ట్రక్చర్స్ నిల్వ చేయడం

స్ట్రక్చర్లు, అర్రేలు లేదా భారీ డేటాను టెక్స్ట్‌గా మార్చకుండా నేరుగా మెమరీ బైట్లుగా ఫైల్‌లో దాచడానికి fwrite() మరియు fread() ఫంక్షన్లను ఉపయోగిస్తాం.

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

ఇవి నాలుగు పారామీటర్లను తీసుకుంటాయి:

  1. ptr: రాయాల్సిన లేదా చదవాల్సిన వేరియబుల్ మెమరీ అడ్రస్.
  2. size: ఒక్కో ఐటమ్ సైజు (ఉదా: sizeof(Employee)).
  3. count: మొత్తం ఎన్ని ఐటమ్స్ ప్రాసెస్ చేయాలి.
  4. stream: ఓపెన్ చేయబడిన బైనరీ ఫైల్ పాయింటర్.

పూర్తి స్ట్రక్చర్‌ను ఒకే ఒక్క కాల్‌తో బైనరీ ఫైల్‌లో సేవ్ చేయడం:

C — High-Speed Binary I/O (fwrite & fread)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int id;
    char name[30];
    double salary;
} Employee;

int main() {
    Employee emp_out[2] = {
        {101, "రాజేష్ వర్మ", 75000.50},
        {102, "సునీత రావు", 88000.00}
    };

    // 1. బైనరీ ఫైల్‌లో రాయడం ("wb" - Write Binary)
    FILE *fp = fopen("employees.bin", "wb");
    if (!fp) { perror("ఫైల్ ఓపెన్ ఫెయిల్"); return 1; }

    // ఒకే ఒక్క CPU ఇన్‌స్ట్రక్షన్‌తో మొత్తం 2 స్ట్రక్చర్ల డేటాను డిస్క్‌పై రాయడం
    size_t written = fwrite(emp_out, sizeof(Employee), 2, fp);
    fclose(fp);
    printf("✅ %zu ఉద్యోగుల రికార్డులు బైనరీ ఫైల్‌లో భద్రపరచబడ్డాయి.\n", written);

    // 2. బైనరీ ఫైల్ నుండి చదవడం ("rb" - Read Binary)
    fp = fopen("employees.bin", "rb");
    if (!fp) { perror("రీడ్ ఫెయిల్"); return 1; }

    Employee emp_in;
    printf("\n--- బైనరీ ఫైల్ నుండి చదివిన ఉద్యోగుల వివరాలు ---\n");
    while (fread(&emp_in, sizeof(Employee), 1, fp) == 1) {
        printf("ID: %d | పేరు: %-15s | జీతం: ₹%.2f\n", emp_in.id, emp_in.name, emp_in.salary);
    }

    fclose(fp);
    return 0;
}

10 రాండమ్ యాక్సెస్: fseek(), ftell(), rewind() తో ఫైల్ కర్సర్ నావిగేషన్

సాధారణంగా ఫైల్ ఆపరేషన్లు మొదటి అక్షరం నుండి వరుసగా (Sequential Access) జరుగుతాయి. కానీ 10 లక్షల రికార్డులు ఉన్న భారీ ఫైల్‌లో నేరుగా 500వ రికార్డును చదవాలంటే లేదా మార్చాలంటే మొత్తం ఫైల్ చదవడం సమయం వృధా! దీనిని నివారించడానికి C మనకు రాండమ్ యాక్సెస్ (Random Access) ఫంక్షన్లను అందిస్తుంది:

  • int fseek(FILE *stream, long offset, int origin); — కర్సర్‌ను మనకు నచ్చిన స్థానానికి తరలిస్తుంది.
  • long ftell(FILE *stream); — ఫైల్ ప్రారంభం నుండి కర్సర్ ప్రస్తుతం ఎన్నో బైట్ వద్ద ఉందో తెలుపుతుంది.
  • void rewind(FILE *stream); — కర్సర్‌ను తిరిగి ఫైల్ మొదటికి (0 బైట్) తెస్తుంది.

fseek() లో 3 మూల స్థానాలు (Origins):

కాన్‌స్టాంట్ అర్థం ఉదాహరణ వినియోగం
SEEK_SET ఫైల్ ప్రారంభ స్థానం (Beginning) fseek(fp, 100, SEEK_SET); — ప్రారంభం నుండి 100వ బైట్‌కు వెళ్లడం.
SEEK_CUR ప్రస్తుత కర్సర్ స్థానం (Current) fseek(fp, 20, SEEK_CUR); — ప్రస్తుత స్థానం నుండి 20 బైట్లు ముందుకు వెళ్లడం.
SEEK_END ఫైల్ ముగింపు స్థానం (End of File) fseek(fp, 0, SEEK_END); — సరిగ్గా ఫైల్ చివరకు చేరుకోవడం.
⚡ ఫైల్ సైజును కనుగొనే గోల్డెన్ ట్రిక్:
fseek(fp, 0, SEEK_END); చేసి, వెంటనే long size = ftell(fp); పిలిస్తే, ఆ ఫైల్ మొత్తం ఎన్ని బైట్లు ఉందో తక్షణమే తెలిసిపోతుంది!

11 బఫరింగ్, fflush() మరియు ఫైల్ ఎర్రర్ హ్యాండ్లింగ్ (feof & ferror)

ప్రతిసారి మనం ఒక అక్షరాన్ని రాసినప్పుడు ఆపరేటింగ్ సిస్టమ్ హార్డ్ డిస్క్‌ను తిప్పదు. వేగం కోసం C లైబ్రరీ ముందుగా డేటాను RAM లోని ఒక ఇంటర్నల్ బఫర్‌లో దాచిపెడుతుంది. బఫర్ నిండినప్పుడు మాత్రమే డిస్క్‌పై రాస్తుంది.

కానీ ఏదైనా కారణం చేత కంప్యూటర్ పవర్ పోతే బఫర్‌లోని డేటా డిస్క్‌కు చేరకుండానే నశించిపోతుంది. అందువల్ల అత్యవసర డేటాను తక్షణమే డిస్క్‌కు పంపించడానికి fflush(fp); ను పిలుస్తాం.

🚨 while (!feof(fp)) ఎందుకు ఒక పాపులర్ బగ్?
చాలామంది కోడర్లు while (!feof(fp)) { fscanf(...); } అని రాస్తుంటారు. కానీ feof() ఫంక్షన్ ఫైల్ చివరను తాకినప్పుడు ట్రూ అవ్వదు—ఫైల్ చివర దాటి చదవడానికి ప్రయత్నించి విఫలమైనప్పుడు మాత్రమే ట్రూ అవుతుంది! దీనివల్ల లూప్ చివరి రికార్డును రెండుసార్లు ప్రాసెస్ చేస్తుంది! ఎల్లప్పుడూ fscanf లేదా fgets లేదా fread రిటర్న్ వాల్యూను మాత్రమే లూప్ కండిషన్‌గా వాడాలి!

12 రియల్-వరల్డ్ ప్రాజెక్ట్: పర్సిస్టెంట్ కాంటాక్ట్ డైరెక్టరీ / ఫోన్‌బుక్ సిస్టమ్

ఇప్పుడు మనం నేర్చుకున్న బైనరీ ఫైల్ హ్యాండ్లింగ్, స్ట్రక్చర్స్ మరియు ఫైల్ కర్సర్ కాన్సెప్ట్‌లతో ఒక పూర్తి స్థాయి పర్సిస్టెంట్ కాంటాక్ట్స్ మేనేజర్ (Persistent CLI Phonebook) ను నిర్మిద్దాం. ఈ ప్రోగ్రామ్‌ను క్లోజ్ చేసి మళ్లీ ఓపెన్ చేసినా కాంటాక్ట్స్ సురక్షితంగా ఉంటాయి!

C — Persistent Phonebook Directory Mini-Project
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FILENAME "contacts.dat"

typedef struct {
    int id;
    char name[40];
    char phone[15];
    char email[40];
} Contact;

// 1. కొత్త కాంటాక్ట్‌ను ఫైల్ చివరన చేర్చడం ("ab" mode)
void add_contact(int id, const char *name, const char *phone, const char *email) {
    FILE *fp = fopen(FILENAME, "ab");
    if (!fp) {
        perror("కాంటాక్ట్ ఫైల్ తెరవలేకపోయాం");
        return;
    }

    Contact c;
    c.id = id;
    strncpy(c.name, name, sizeof(c.name) - 1);
    c.name[sizeof(c.name) - 1] = '\0';
    strncpy(c.phone, phone, sizeof(c.phone) - 1);
    c.phone[sizeof(c.phone) - 1] = '\0';
    strncpy(c.email, email, sizeof(c.email) - 1);
    c.email[sizeof(c.email) - 1] = '\0';

    fwrite(&c, sizeof(Contact), 1, fp);
    fclose(fp);
    printf("✅ కాంటాక్ట్ సేవ్ అయింది: %s (%s)\n", name, phone);
}

// 2. అన్ని కాంటాక్ట్‌లను ఫైల్ నుండి చదివి చూపించడం ("rb" mode)
void list_all_contacts() {
    FILE *fp = fopen(FILENAME, "rb");
    if (!fp) {
        printf("⚠️ ఇంకా ఎలాంటి కాంటాక్ట్స్ సేవ్ చేయబడలేదు.\n");
        return;
    }

    Contact c;
    printf("\n==================== ఫోన్‌బుక్ డైరెక్టరీ ====================\n");
    printf("%-5s %-20s %-15s %-25s\n", "ID", "పేరు", "ఫోన్ నంబర్", "ఈమెయిల్");
    printf("------------------------------------------------------------\n");

    int count = 0;
    while (fread(&c, sizeof(Contact), 1, fp) == 1) {
        printf("%-5d %-20s %-15s %-25s\n", c.id, c.name, c.phone, c.email);
        count++;
    }
    printf("============================================================\n");
    printf("మొత్తం కాంటాక్ట్స్: %d\n\n", count);
    fclose(fp);
}

// 3. పేరు ఆధారంగా వెతకడం
void search_contact(const char *search_name) {
    FILE *fp = fopen(FILENAME, "rb");
    if (!fp) {
        printf("ఫైల్ అందుబాటులో లేదు.\n");
        return;
    }

    Contact c;
    int found = 0;
    while (fread(&c, sizeof(Contact), 1, fp) == 1) {
        if (strstr(c.name, search_name) != NULL) {
            printf("🔍 దొరికింది! -> ID: %d | పేరు: %s | ఫోన్: %s | ఈమెయిల్: %s\n", 
                   c.id, c.name, c.phone, c.email);
            found = 1;
        }
    }

    if (!found) {
        printf("❌ '%s' పేరుతో ఎలాంటి కాంటాక్ట్ దొరకలేదు.\n", search_name);
    }
    fclose(fp);
}

int main() {
    printf("=== పర్సిస్టెంట్ ఫోన్‌బుక్ సిస్టమ్ ప్రారంభం ===\n");

    // టెస్ట్ కోసం కొన్ని కాంటాక్ట్‌లను జోడించడం
    add_contact(1, "శ్రీధర్ పట్నాయక్", "9848022338", "sridhar@gmail.com");
    add_contact(2, "భార్గవి కొమర్రాజు", "9988776655", "bhargavi@yahoo.com");
    add_contact(3, "కిరణ్ కుమార్", "9123456789", "kiran@outlook.com");

    // మొత్తం లిస్ట్‌ను డిస్‌ప్లే చేయడం
    list_all_contacts();

    // సెర్చ్ టెస్టింగ్
    printf("వెతుకుతున్నాం 'భార్గవి'...\n");
    search_contact("భార్గవి");

    return 0;
}

🚀 లైవ్ C కంపైలర్‌లో రన్ చేసి చూడండి!

ఫైల్ క్రియేషన్ మరియు బైనరీ రికార్డ్స్ ఎలా ప్రాసెస్ అవుతాయో మన ఆన్‌లైన్ C కంపైలర్‌లో స్వయంగా ఎగ్జిక్యూట్ చేయండి!

Online C Compiler ఓపెన్ చేయండి ➔

13 సారాంశం, ఇంటర్వ్యూ ప్రశ్నలు & సెల్ఫ్-అసెస్మెంట్ క్విజ్

కీలక సారాంశం (Key Takeaways):

  • Data Persistence: RAM తాత్కాలికం; ఫైల్స్ ద్వారా డేటా డిస్క్‌లో శాశ్వతంగా భద్రపరచబడుతుంది.
  • FILE* Pointer: ఫైల్ స్ట్రీమ్, బఫర్ మరియు కర్సర్ లొకేషన్‌ను నిర్వహించే కీ హ్యాండిల్.
  • Defensive fopen(): ఎల్లప్పుడూ if (fp == NULL) చెక్ చేయాలి మరియు perror() వాడాలి.
  • Character & String I/O: fgetc/fputc ఒక్కో అక్షరానికి; fgets/fputs భద్రమైన లైన్ రీడింగ్‌కు.
  • Formatted vs Binary: fprintf/fscanf టెక్స్ట్ ఫైల్స్‌కు; fwrite/fread హై-స్పీడ్ బైనరీ డేటాకు.
  • Random Access: fseek() మరియు ftell() ద్వారా నేరుగా కావలసిన బైట్‌కు జంప్ చేయవచ్చు.
  • fclose(): బఫర్లను డిస్క్‌పై రాసి OS హ్యాండిల్‌ను విడుదల చేయడానికి తప్పనిసరి.

టాప్ 5 C ఫైల్ హ్యాండ్లింగ్ ఇంటర్వ్యూ ప్రశ్నలు:

  1. Q1: fclose() చేయడం మర్చిపోతే ఏమవుతుంది?
    జవాబు: బఫర్‌లో ఉన్న డేటా డిస్క్‌పై రాయబడకపోవచ్చు (Data Loss). అలాగే OS ఫైల్ హ్యాండిల్స్ పరిమిత సంఖ్యలో ఉంటాయి కాబట్టి ఇతర ప్రోగ్రామ్‌లు ఆ ఫైల్‌ను లాక్ చేయడంలో విఫలమవ్వచ్చు.
  2. Q2: Windows లో టెక్స్ట్ మోడ్ ("r") మరియు బైనరీ మోడ్ ("rb") మధ్య తేడా ఏమిటి?
    జవాబు: విండోస్ టెక్స్ట్ మోడ్‌లో రీడ్ చేసేటప్పుడు \r\n ఆటోమేటిక్‌గా ఒకే \n గా మారుతుంది. బైనరీ మోడ్‌లో ఎలాంటి మార్పు లేకుండా ప్రతి బైట్ ఖచ్చితంగా యథాతథంగా చదవబడుతుంది.
  3. Q3: remove() మరియు rename() ఫంక్షన్లు ఎలా పనిచేస్తాయి?
    జవాబు: <stdio.h> లో ఉండే remove("file.txt") ఫైల్‌ను డిలీట్ చేస్తుంది; rename("old.txt", "new.txt") ఫైల్ పేరును లేదా పాత్‌ను మారుస్తుంది.
  4. Q4: rewind(fp) ఫంక్షన్ దేనికి సమానం?
    జవాబు: (void)fseek(fp, 0L, SEEK_SET); తో పాటు ఫైల్ ఎర్రర్ ఇండికేటర్‌ను క్లియర్ చేస్తుంది.
  5. Q5: fgets() మరియు fscanf("%s") మధ్య భద్రతా వ్యత్యాసం ఏమిటి?
    జవాబు: fscanf("%s", str) స్పేస్ రాగానే ఆగిపోతుంది మరియు బౌండరీ చెక్ లేకపోతే బఫర్ ఓవర్‌ఫ్లో చేస్తుంది. fgets(str, size, fp) ఖచ్చితంగా size పరిమితికి లోబడి మాత్రమే చదువుతుంది మరియు స్పేస్‌లతో కూడిన మొత్తం వాక్యాన్ని రీడ్ చేస్తుంది.

ఇంటరాక్టివ్ సెల్ఫ్-అసెస్మెంట్ క్విజ్ (Self-Assessment Quiz):

❓ ప్రశ్న 1: ఇప్పటికే ముఖ్యమైన డేటా ఉన్న ఫైల్‌ను fopen("data.txt", "w") తో తెరిస్తే ఏమి జరుగుతుంది?
సమాధానం: ఆ ఫైల్‌లోని పాత డేటా మొత్తం తక్షణమే తుడిచివేయబడుతుంది (Truncated to 0 bytes)! పాత డేటాను కాపాడుకోవాలంటే "a" (append) మోడ్ వాడాలి.
❓ ప్రశ్న 2: fgetc() ఫంక్షన్ రిటర్న్ టైప్ char కాకుండా int ఎందుకు ఉంటుంది?
సమాధానం: అన్ని 256 బైనరీ క్యారెక్టర్లతో పాటు, ఫైల్ ముగింపును తెలిపే ప్రత్యేక EOF (-1) విలువను స్పష్టంగా గుర్తించడానికి int అవసరం.
❓ ప్రశ్న 3: while (!feof(fp)) అని రాయడం ఎందుకు తప్పు?
సమాధానం: feof() ఫైల్ ముగింపును తాకినప్పుడు ట్రూ అవ్వదు; ఫైల్ దాటి చదవడానికి ప్రయత్నించి ఫెయిల్ అయినప్పుడు మాత్రమే ట్రూ అవుతుంది. దీనివల్ల లూప్ చివరి రికార్డును రెండుసార్లు ఎగ్జిక్యూట్ చేస్తుంది.
❓ ప్రశ్న 4: ఒక బైనరీ ఫైల్ సైజును కనుగొనడానికి ఏ రెండు ఫంక్షన్ల కాంబినేషన్ వాడతారు?
సమాధానం: fseek(fp, 0, SEEK_END); ద్వారా కర్సర్‌ను చివరికి తీసుకెళ్లి, ఆపై long size = ftell(fp); ద్వారా ఫైల్ సైజును బైట్లలో పొందుతారు.
❓ ప్రశ్న 5: RAM లోని డేటాను ఒకేసారి వేగంగా ఫైల్‌లో బైనరీగా రాయడానికి ఏ ఫంక్షన్ వాడతారు?
సమాధానం: fwrite() ఫంక్షన్. ఇది ఎలాంటి టెక్స్ట్ కన్వర్షన్ లేకుండా మెమరీ బైట్లను నేరుగా డిస్క్‌పై రాస్తుంది.
← Prev: 18. డైనమిక్ మెమరీ (DMA) Next: 20. గ్రాండ్ మినీ ప్రాజెక్ట్ →
OC
Written by Our Compiler Technical Editorial Team
Reviewed for accuracy & tested on GCC 13+ / Clang 17 runtime · 2026 Academic Edition