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.
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
| İfade | Anlamı |
|---|---|
int *p; | p, bir int’in adresini tutabilir |
p = &x; | p artık x’i gösterir |
*p | p’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 |
NULL | hiç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 daa[-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:
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)++
| İfade | Ne 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 |
++*p | değ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
| İfade | Değer | Neden |
|---|---|---|
strlen("hello") | 5 | null’a kadar sayar |
sizeof(char s[] = "hello") | 6 | null dahil yer |
printf("%s", s + 2) | llo | s[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 == biki dizinin adreslerini karşılaştırır. İçerik aynı olsa da adresler farklıdır, sonuç 0. İçerik içinstrcmp(a, b) == 0kullan.
char a[] = "abc", b[] = "abc";
printf("%d %d\n", a == b, strcmp(a, b) == 0);
0 1
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
int *p = &x; p = 9; x’i değiştirir | p’nin adresini bozar; *p = 9 yaz |
q = p değeri kopyalar | Oku kopyalar, ikisi aynı yeri gösterir |
*p++ değeri artırır | Pointer’ı ilerletir; değer için (*p)++ |
| Diziyi fonksiyona geçirmek kopyalar | Adres geçer, asıl dizi değişir |
&a[4] - &a[1] = 12 (byte) | 3 eleman |
| strlen(“hello”) = 6 | 5; sizeof 6 |
a == b string içeriğini karşılaştırır | Adresleri 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ç.
What is printed?
int x = 5;
int *p = &x;
*p = 9;
printf("%d\n", x);Cevabı göster
p, x'in adresini tutar. *p = 9, 'p'nin gösterdiği yere 9 yaz' demektir, yani x = 9.
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
*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.
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
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.
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
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).
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
*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.
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
(*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.
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
Dizi fonksiyona kopyalanmaz; ilk elemanın adresi geçer (int v[] aslında int *v). Fonksiyon asıl diziyi değiştirir.
Given int a[6];, what is the value of &a[4] - &a[1]?
Cevabı göster
Pointer çıkarma, aradaki ELEMAN sayısını verir (byte değil). 4 - 1 = 3. Pointer aritmetiği tipin boyutunu otomatik hesaba katar.
What is printed?
char s[] = "hello";
s[0] = 'j';
printf("%s %d %d\n", s, (int) strlen(s), (int) sizeof(s));Cevabı göster
s bir char dizisidir, değiştirilebilir. strlen null'a ('\0') kadar sayar: 5. sizeof dizinin bellekte kapladığı yer: 5 karakter + 1 null = 6.
What is printed?
char s[] = "hello";
char *p = s + 2;
printf("%s %c\n", p, *(p + 1));Cevabı göster
p, s[2]'yi gösterir. %s oradan null'a kadar basar: llo. *(p + 1) = s[3] = 'l'.
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
İki uçtan ortaya doğru takas: (s,k), (t,c), ortadaki a yerinde kalır. Sonuç kcats.
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
&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ı.