Programming Language & Its Applications — Pointers, Structure and Data Files in C, NEC licence examination syllabus (Nepal Engineering Council).
By value copies the whole thing; by pointer copies 8 bytes. For anything bigger than a few words, pass a pointer.
Structs, unlike arrays, do not decay. Passing one by value genuinely copies every byte — which is safe but can be expensive. Passing a pointer copies only an address, allows modification, and with const still protects the original. This topic covers all three styles plus returning structs, and quantifies the cost so the choice is not guesswork.
#include <stdio.h>
#include <string.h>
typedef struct {
int roll;
char name[20];
float marks;
} Student;
/* by value - gets a copy, cannot change the original */
void by_value(Student s) {
s.marks = 0;
strcpy(s.name, "CHANGED");
printf(" inside by_value: %s %.1f\n", s.name, s.marks);
}
/* by pointer - can change the original */
void by_pointer(Student *s) {
s->marks += 5.0f; /* grace marks */
}
/* const pointer - cheap AND safe */
void print(const Student *s) {
printf(" %d %-14s %.1f\n", s->roll, s->name, s->marks);
/* s->marks = 0; <-- compile error, good */
}
/* returning a struct by value */
Student make(int roll, const char *name, float marks) {
Student s;
s.roll = roll;
strncpy(s.name, name, sizeof s.name - 1);
s.name[sizeof s.name - 1] = '\0';
s.marks = marks;
return s;
}
int main(void) {
Student a = make(101, "Ram Bahadur", 87.5f);
printf("original:\n"); print(&a);
by_value(a);
printf("after by_value:\n"); print(&a);
by_pointer(&a);
printf("after by_pointer (+5):\n"); print(&a);
return 0;
}
Output:
original:
101 Ram Bahadur 87.5
inside by_value: CHANGED 0.0
after by_value:
101 Ram Bahadur 87.5 <-- unchanged
after by_pointer (+5):
101 Ram Bahadur 92.5 <-- changed
#include <stdio.h>
typedef struct {
char name[64];
char address[128];
char email[64];
int marks[10];
double history[32];
} BigRecord; /* ~552 bytes */
double sum_by_value(BigRecord r) { /* copies 552 B */
double s = 0;
for (int i = 0; i < 32; i++) s += r.history[i];
return s;
}
double sum_by_ptr(const BigRecord *r) { /* copies 8 B */
double s = 0;
for (int i = 0; i < 32; i++) s += r->history[i];
return s;
}
int main(void) {
BigRecord r = {0};
for (int i = 0; i < 32; i++) r.history[i] = i * 1.5;
printf("sizeof(BigRecord) = %zu bytes\n", sizeof r);
printf("sizeof(BigRecord*) = %zu bytes\n", sizeof(BigRecord*));
printf("by value = %.1f by pointer = %.1f\n",
sum_by_value(r), sum_by_ptr(&r));
printf("\nFor 1,000,000 calls:\n");
printf(" by value : %.1f MB copied\n",
1000000.0 * sizeof(r) / 1048576.0);
printf(" by pointer: %.1f MB copied\n",
1000000.0 * sizeof(BigRecord*) / 1048576.0);
return 0;
}
Output:
sizeof(BigRecord) = 552 bytes
sizeof(BigRecord*) = 8 bytes
by value = 744.0 by pointer = 744.0
For 1,000,000 calls:
by value : 526.4 MB copied
by pointer: 7.6 MB copied
#include <stdio.h>
#include <math.h>
typedef struct { double re, im; } Complex; /* 16 bytes */
/* small struct: by value is idiomatic and fast */
Complex c_add(Complex a, Complex b) {
return (Complex){ a.re + b.re, a.im + b.im };
}
Complex c_mul(Complex a, Complex b) {
return (Complex){ a.re*b.re - a.im*b.im,
a.re*b.im + a.im*b.re };
}
double c_abs(Complex a) { return sqrt(a.re*a.re + a.im*a.im); }
void c_print(const char *tag, Complex a) {
printf("%-8s %.2f %c %.2fi\n", tag,
a.re, a.im < 0 ? '-' : '+', fabs(a.im));
}
int main(void) {
Complex p = {3.0, 4.0}, q = {1.0, -2.0};
c_print("p", p); c_print("q", q);
c_print("p+q", c_add(p, q));
c_print("p*q", c_mul(p, q));
printf("|p| = %.4f\n", c_abs(p));
return 0;
}
Output:
p 3.00 + 4.00i
q 1.00 - 2.00i
p+q 4.00 + 2.00i
p*q 11.00 - 2.00i
|p| = 5.0000
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct { int id; char name[20]; } Rec;
/* WRONG: s lives on the stack and dies at return */
/* Rec *bad(void) { Rec s = {1,"x"}; return &s; } */
/* RIGHT 1: return by value - the struct is copied out */
Rec ok_value(int id, const char *n) {
Rec s; s.id = id;
strncpy(s.name, n, 19); s.name[19] = '\0';
return s;
}
/* RIGHT 2: allocate on the heap - caller must free */
Rec *ok_heap(int id, const char *n) {
Rec *s = malloc(sizeof *s);
if (!s) return NULL;
s->id = id;
strncpy(s->name, n, 19); s->name[19] = '\0';
return s;
}
/* RIGHT 3: caller supplies the storage - no allocation */
void ok_fill(Rec *out, int id, const char *n) {
out->id = id;
strncpy(out->name, n, 19); out->name[19] = '\0';
}
int main(void) {
Rec a = ok_value(1, "by value");
printf("%d %s\n", a.id, a.name);
Rec *b = ok_heap(2, "on heap");
if (b) { printf("%d %s\n", b->id, b->name); free(b); }
Rec c;
ok_fill(&c, 3, "caller storage");
printf("%d %s\n", c.id, c.name);
return 0;
}
Output:
1 by value
2 on heap
3 caller storage
Pattern 3 — the caller supplies the buffer — is what the C standard library itself uses (fgets, sprintf, strcpy). It avoids allocation entirely and makes ownership unambiguous: whoever declared the storage frees it. When you design a C API, prefer it.
Create a free account to tick topics off, take notes as you read, watch the video lessons and get a day-by-day study plan built around your exam date.
Loading…