SEN2201 · 13. hafta · Programming in C

C'de pointer'lar, diziler ve string'ler

Adres (&) ve içerik (*) operatörleri, pointer diyagramları, pointer ile takas, diziler ve pointer aritmetiği, fonksiyona dizi geçirmek, null ile biten string'ler ve LC-3 bellek düzeni.

pointeraddress operator (&)indirection / dereference (*)NULLcall by referencearrayindexpointer arithmeticarray name as pointerstringnull terminatorstrlenstrcmpbuffer overflow

Pointer, C’yi öğrenirken en çok korkulan konu. Oysa bu derste zaten bildiğin bir şey: adres. LC-3’te LDR R1, R2, #0 yazdığında R2 bir pointer’dı; içinde bir değer değil, bir bellek adresi vardı. C’deki *p tam olarak budur. Bu sayfa pointer’ları, dizileri ve string’leri birlikte anlatıyor, çünkü C’de üçü aynı fikrin parçası.

Sınav soruları: pointer diyagramı çizerek “What is printed?”, dizi ve pointer aritmetiği, string işleme fonksiyonları, LC-3 bellek adresi hesabı.

Adres ve pointer

Her değişken bellekte bir konumdadır. & operatörü o konumun adresini verir. Bir pointer değişkeni, adres saklayan değişkendir.

int x = 42;
int *p = &x;        /* p, x'in adresini tutar */
printf("%d %d\n", *p, p == &x);
*p = *p + 8;        /* x = 50 */
printf("%d\n", x);
42 1
50
İfadeAnlamı
int *p;p, bir int’in adresini tutabilir
p = &x;p artık x’i gösterir
*pp’nin gösterdiği yerdeki değer (dereference)
*p = 9;gösterilen yere 9 yaz
p = q;p de q’nun gösterdiği yeri gösterir
NULLhiçbir yeri göstermeyen pointer

* iki anlamda kullanılır: tanımda (int *p) “pointer tipi”, ifadede (*p) “gösterdiği değer”.

Pointer diyagramı

Her soruda kutular ve oklar çiz. Kutu = değişken, ok = pointer.

int a = 3, b = 7;  int *p = &a, *q = &b;

 p ──► [a: 3]        q ──► [b: 7]

*p = *q + 1;        p ──► [a: 8]        q ──► [b: 7]
q = p;              p ──► [a: 8] ◄── q        [b: 7]
*q = *q * 2;        p ──► [a: 16] ◄── q       [b: 7]

Sonuç: a = 16, b = 7. q = p satırı değer kopyalamaz, oku kopyalar.

LC-3 karşılığı

x yerel bir değişken (R5 + 0), p ikinci yerel (R5 - 1) olsun:

        ADD  R0, R5, #0     ; R0 = &x
        STR  R0, R5, #-1    ; p = &x
        LDR  R1, R5, #-1    ; R1 = p
        LDR  R2, R1, #0     ; R2 = *p

&x bir adres hesabıdır (ADD ya da LEA), *p bir LDR’dir.

Pointer ile “referansla çağırma”

C’de her şey değerle geçer. Ama değer bir adres ise, fonksiyon o adresteki asıl değişkeni değiştirebilir.

void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }

int x = 1, y = 2;
swap(&x, &y);           /* x = 2, y = 1 */

Bir fonksiyondan birden fazla sonuç döndürmenin yolu da budur:

void minmax(int a[], int n, int *mn, int *mx) {
    int i;
    *mn = *mx = a[0];
    for (i = 1; i < n; i++) {
        if (a[i] < *mn) *mn = a[i];
        if (a[i] > *mx) *mx = a[i];
    }
}

int a[5] = {4, -1, 9, 3, 0}, mn, mx;
minmax(a, 5, &mn, &mx);
printf("%d %d\n", mn, mx);
-1 9

scanf("%d", &x); satırındaki & da bu yüzden var: scanf okuduğu sayıyı x’in adresine yazar.

Diziler

int a[5]; bellekte art arda 5 int ayırır. İndeks 0’dan başlar: a[0] … a[4].

LC-3’te (int = bir kelime) a x4010’da başlıyorsa:

adres:  x4010  x4011  x4012  x4013  x4014
       [a[0]] [a[1]] [a[2]] [a[3]] [a[4]]

a[i]’nin adresi = taban + i. Dizi yerel ise yığında, global ise global bölgede durur.

TUZAK · sınır kontrolü yok. C, a[5] ya da a[-1] yazmanı engellemez; sadece o adresteki belleği okur ya da ezer. Yığında bu, başka bir değişkeni ya da dönüş adresini bozabilir: güvenlik açıklarının klasik kaynağı (buffer overflow).

Dizi adı bir adrestir

a, &a[0] ile aynıdır. Bu yüzden:

a[i]≡∗(a+i)p[i]≡∗(p+i)a[i] \equiv *(a + i) \qquad p[i] \equiv *(p + i)

Pointer aritmetiği: p + k, k eleman ileriyi gösterir; adres tipin boyutu kadar artar. İki pointer’ın farkı aradaki eleman sayısıdır.

int a[4] = {7, 3, 9, 1};
int *p;
int s = 0;
for (p = a; p < a + 4; p++)
    s += *p;
printf("%d\n", s);
20

*p++ ile (*p)++

İfadeNe olur?
*p++*(p++): değeri oku, sonra pointer ilerler
(*p)++gösterilen değer bir artar, pointer yerinde
*++pönce pointer ilerler, sonra yeni yeri oku
++*pdeğer artar, artmış değer okunur

Diziyi fonksiyona geçirmek

Dizi kopyalanmaz; ilk elemanın adresi geçer. void f(int v[]) ile void f(int *v) aynıdır. Fonksiyon dizinin boyutunu bilemez, bu yüzden boyut ayrı parametre olarak verilir.

String’ler

C’de string, sonunda null karakteri ('\0', değeri 0) olan bir char dizisidir. LC-3’teki .STRINGZ ile aynı fikir.

char s[] = "Hi";
printf("%d %d %d\n", s[0], s[1], s[2]);
72 105 0
İfadeDeğerNeden
strlen("hello")5null’a kadar sayar
sizeof(char s[] = "hello")6null dahil yer
printf("%s", s + 2)llos[2]‘den null’a kadar

Kendi string fonksiyonların

int mylen(const char *s) {
    int n = 0;
    while (s[n] != '\0') n++;
    return n;
}

void mycopy(char *dst, const char *src) {
    while ((*dst++ = *src++) != '\0')
        ;
}

int countc(const char *s, char c) {
    int k = 0;
    for (; *s; s++)
        if (*s == c) k++;
    return k;
}
char d[20];
mycopy(d, "banana");
printf("%s %d\n", d, countc(d, 'a'));
banana 3

TUZAK · string’leri == ile karşılaştırmak. a == b iki dizinin adreslerini karşılaştırır. İçerik aynı olsa da adresler farklıdır, sonuç 0. İçerik için strcmp(a, b) == 0 kullan.

char a[] = "abc", b[] = "abc";
printf("%d %d\n", a == b, strcmp(a, b) == 0);
0 1

Sık yapılan hatalar

Yanlış düşünceDoğrusu
int *p = &x; p = 9; x’i değiştirirp’nin adresini bozar; *p = 9 yaz
q = p değeri kopyalarOku kopyalar, ikisi aynı yeri gösterir
*p++ değeri artırırPointer’ı ilerletir; değer için (*p)++
Diziyi fonksiyona geçirmek kopyalarAdres geçer, asıl dizi değişir
&a[4] - &a[1] = 12 (byte)3 eleman
strlen(“hello”) = 65; sizeof 6
a == b string içeriğini karşılaştırırAdresleri karşılaştırır
scanf("%d", x)&x gerekir

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1kolay

What is printed?

int x = 5;
int *p = &x;
*p = 9;
printf("%d\n", x);
Cevabı göster
9

p, x'in adresini tutar. *p = 9, 'p'nin gösterdiği yere 9 yaz' demektir, yani x = 9.

Soru 2orta

What is printed?

int a = 3, b = 7;
int *p = &a, *q = &b;
*p = *q + 1;
q = p;
*q = *q * 2;
printf("%d %d\n", a, b);
Cevabı göster
16 7

*p = *q + 1 → a = 7 + 1 = 8. q = p → q artık a'yı gösterir (b'yi değil). *q = *q * 2 → a = 16. b hiç değişmedi: 7.
Diyagram çiz: her adımda okların nereyi gösterdiğini güncelle.

Soru 3kolay

What is printed?

void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }
/* in main: */
int x = 1, y = 2;
swap(&x, &y);
printf("%d %d\n", x, y);
Cevabı göster
2 1

Bu sefer x ve y'nin adresleri geçirilir. swap adreslerdeki değerleri değiştirir. Değerle çağırma hâlâ geçerli (adresler kopyalanır), ama kopyalanan adresler aynı yeri gösterdiği için asıl değişkenler değişir.

Soru 4orta

What is printed?

int a[5] = {3, 1, 4, 1, 5};
int *p = a;
printf("%d %d %d\n", *(p + 2), p[4], *a + 1);
Cevabı göster
4 5 4

Dizi adı ilk elemanın adresidir: p = &a[0]. *(p + 2) = a[2] = 4. p[4] = *(p + 4) = 5. *a + 1: önce *a = 3, sonra + 1 = 4 (* önceliği + dan yüksek).

Soru 5zor

What is printed?

int a[] = {10, 20, 30};
int *p = a;
int x = *p++;
printf("%d %d %d\n", x, *p, a[0]);
Cevabı göster
10 20 10

*p++ = *(p++): p'nin eski değeri kullanılır (x = a[0] = 10), sonra POINTER bir eleman ilerler. *p = a[1] = 20. Dizi değişmedi.

Soru 6zor

What is printed?

int a[] = {10, 20, 30};
int *p = a;
int x = (*p)++;
printf("%d %d %d\n", x, *p, a[0]);
Cevabı göster
10 11 11

(*p)++ gösterilen DEĞERİ artırır (post-increment): x eski değeri alır (10), a[0] 11 olur. p hâlâ a[0]'ı gösterir.

Soru 7orta

What is printed?

void dbl(int v[], int n) { int i; for (i = 0; i < n; i++) v[i] *= 2; }
/* in main: */
int v[3] = {1, 2, 3};
dbl(v, 3);
printf("%d %d %d\n", v[0], v[1], v[2]);
Cevabı göster
2 4 6

Dizi fonksiyona kopyalanmaz; ilk elemanın adresi geçer (int v[] aslında int *v). Fonksiyon asıl diziyi değiştirir.

Soru 8orta

Given int a[6];, what is the value of &a[4] - &a[1]?

Cevabı göster
3

Pointer çıkarma, aradaki ELEMAN sayısını verir (byte değil). 4 - 1 = 3. Pointer aritmetiği tipin boyutunu otomatik hesaba katar.

Soru 9orta

What is printed?

char s[] = "hello";
s[0] = 'j';
printf("%s %d %d\n", s, (int) strlen(s), (int) sizeof(s));
Cevabı göster
jello 5 6

s bir char dizisidir, değiştirilebilir. strlen null'a ('\0') kadar sayar: 5. sizeof dizinin bellekte kapladığı yer: 5 karakter + 1 null = 6.

Soru 10orta

What is printed?

char s[] = "hello";
char *p = s + 2;
printf("%s %c\n", p, *(p + 1));
Cevabı göster
llo l

p, s[2]'yi gösterir. %s oradan null'a kadar basar: llo. *(p + 1) = s[3] = 'l'.

Soru 11orta

What is printed?

void reverse(char s[]) {
    int i = 0, j = strlen(s) - 1;
    while (i < j) {
        char t = s[i]; s[i] = s[j]; s[j] = t;
        i++; j--;
    }
}
/* in main: */
char w[] = "stack";
reverse(w);
printf("%s\n", w);
Cevabı göster
kcats

İki uçtan ortaya doğru takas: (s,k), (t,c), ortadaki a yerinde kalır. Sonuç kcats.

Soru 12zor

On the LC-3 (an int takes one 16-bit memory location), int a[4] starts at address x4010 and int *p = &a[1];. What address does p + 2 hold, and which element is it?

Cevabı göster
x4013, a[3]

&a[1] = x4010 + 1 = x4011. p + 2 = x4011 + 2 = x4013 = &a[3]. LC-3 word-addressable olduğu ve int bir kelime olduğu için her eleman bir adres ilerler. Byte-addressable 32 bitlik bir makinede adım 4 byte olurdu.

Doğrulama: Tüm kod parçaları C99 olarak Zig cc (clang) ile derlenip çalıştırıldı, çıktılar birebir kopyalandı (verify/SEN2201/c/w13.c, node verify/SEN2201/c/run.mjs w13). LC-3 adres soruları tek kelimelik int varsayımıyla elle hesaplandı.

Deftere not kartı

&x = x'in ADRESİ | *p = p'nin gösterdiği DEĞER | int *p = &x; *p = 9 -> x = 9 | p = q -> p de q'nun gösterdiğini gösterir (değer değişmez) swap(&x, &y) ile takas olur: adres geçir, *a ile değiştir scanf("%d", &x): adres ister Dizi adı = &a[0] | a[i] == *(a + i) == p[i] p + k -> k ELEMAN ileri | &a[4] - &a[1] = 3 (eleman sayısı) *p++ -> değeri al, POINTER ilerle | (*p)++ -> DEĞER artar Diziyi fonksiyona geçir = adres geçir -> asıl dizi değişir String = char dizisi + '\0' | "hello": strlen 5, sizeof 6 %s null'a kadar basar | s + 2 -> "llo" a == b string'lerde ADRES karşılaştırır -> strcmp(a,b) == 0 kullan LC-3: int = 1 kelime, &a[i] = taban + i | sınır kontrolü yok (buffer overflow)