[Çözüldü] Gösterici aritmetiğinde artırma

Başlatan Neof07, 25 Temmuz 2016 - 12:05:55

« önceki - sonraki »

0 Üyeler ve 1 Ziyaretçi konuyu incelemekte.

Neof07

Merhaba arkadaşlar altta denemek için yazdığım kod var ve çıktısı ise şöyle :
#include <stdio.h>
main(){
int say=9,*p;
p=&say;
printf("%d - %p\n%p",say,p,p++);
}


Çıktı: 9 - 0x7ffe5355fee8
0x7ffe5355fee4
Ben burada 1 artır dedim ancak o 1 azalttı. Printf içinde yazınca böyle yapıyor. Tek başına bir deyim olarak yazdığımda yani şöyle ;
#include <stdio.h>
main(){
int say=9,*p;
p=&say;
printf("%d - %p",say,p);
p++;
printf("%p",p);
}


Böyle yaptığımda ancak bir artırıyor yani  - p=p+1*sizeof(int);
-* Anlamak istediğim kısım neden printf() içinde böyle yapıyor ?

Amenofis

#1
Olayı daha iyi anlamak için kodu şu şekle getirip öyle bakalım.

#include <stdio.h>

void yazdir(int *a, int *b)
{
printf("%p\n", a);
printf("%p\n", b);
}

int main()
{
int say=9, *p;
p= &say;
yazdir(p, p++);
}


Dil standartlarına dahil edilmeyip derleyicilerin keyfine bırakılan bazı durumlar var. Bu durum da onlardan biri.

Muhtemelen fonksiyona geçirilen parametrelerin soldan sağa doğru işlediğini düşünüyorsun, ancak gcc parametreleri sağdan sola doğru fonksiyona geçirir. Bir de işin ++p ve p++ farkı var. p++ ifadesinde önce p geçirilir, ardından arttırılır. Bu yüzden önce p'nin önceki değeri geçiyor ve printf bu değeri alıyor. Ardından p arttırılıyor ve soldaki parametre geçiyor yani p'nin güncel değeri. Yazdırma işlemi bütün parametrelerin değerleri kopyalandıktan sonra yapılıyor.

Sisteminde clang kurulu ise onunla da deneyebilirsin. Ben kendi sistemimde (Aarch64) denediğimde clang'ın soldan sağa doğru geçirdiğini gördüm, yani gcc'nin tersi. Bu yüzden iki derleyicide çıktılar farklı. Kod yazarken bu gibi belirsiz durumlardan kaçınmak gerekli. Derleyicinize "-Wall" parametresini verirseniz sizi uyaracaktır zaten.

bugra9

#2
Olay ++ operatörünün işleyişinden kaynaklanmakta. Öncelikle bu operatörü fonksiyon gibi düşünebilirsin ve bu fonksiyonların nasıl çalıştığını yazayım.

++i sözde kod

function ikiArtiOnde(i) {
    i = i + 1
    return i
}


i++ sözde kod

function ikiArtiArkada(i) {
    temp = i
    i = i + 1
    return temp
}


Farkettiysen birisinde değeri arttırıp yeni halini döndürürken diğerinde değerini arttırıp eski halinin yedeğini döndürüyor.

Şimdi koduna bakalım.
printf("%d - %p\n%p",say,p,p++);
Burada bu fonksiyonun aldığı parametrelere dikkat edelim. İlk ve ikinci parametre direk alınıyor ama üçüncü parametre fonksiyon gibi çalıştırılarak alınıyor. Yani 3. parametre olarak p'nin eski değerinin yedeği alınıyor. Sonra da p değeri arttırılıyor.

Şimdi yazdırma kısmını düşünelim.
- İkinci değer direk alındığı ve p ile aynı olduğu için, 3. değer işlenirken de p'nin değeri bir arttırıldığı için otomatik p+1 i ekrana basıyor.
- Üçüncü değer ise p'nin eski yedeği olduğu ve p+1 işleminden etkilenmediği için ekrana p değerini basıyor.
Böylece sana azaltılmış gibi görünüyor, olay bundan ibaret.

Konusu açılmışken küçük bir şeyden bahsedeyim.
for(;; i++)
kullanımı yanlış olup doğrusu
for(;; ++i)
şeklindedir. For ile i değerinin sadece 1 arttırılması beklenmekte olup fonksiyonun ne döndüreceği önemli değildir. Bundan dolayı ++i, i++'ya göre daha hızlı çalıştığı için bunu kullanmak daha doğrudur.

Neof07

#3
Alıntı yapılan: Amenofis - 25 Temmuz 2016 - 15:37:31
Olayı daha iyi anlamak için kodu şu şekle getirip öyle bakalım.

#include <stdio.h>

void yazdir(int *a, int *b)
{
printf("%p\n", a);
printf("%p\n", b);
}

int main()
{
int say=9, *p;
p= &say;
yazdir(p, p++);
}


Dil standartlarına dahil edilmeyip derleyicilerin keyfine bırakılan bazı durumlar var. Bu durum da onlardan biri.

Muhtemelen fonksiyona geçirilen parametrelerin soldan sağa doğru işlediğini düşünüyorsun, ancak gcc parametreleri sağdan sola doğru fonksiyona geçirir. Bir de işin ++p ve p++ farkı var. p++ ifadesinde önce p geçirilir, ardından arttırılır. Bu yüzden önce p'nin önceki değeri geçiyor ve printf bu değeri alıyor. Ardından p arttırılıyor ve soldaki parametre geçiyor yani p'nin güncel değeri. Yazdırma işlemi bütün parametrelerin değerleri kopyalandıktan sonra yapılıyor.

Sisteminde clang kurulu ise onunla da deneyebilirsin. Ben kendi sistemimde (Aarch64) denediğimde clang'ın soldan sağa doğru geçirdiğini gördüm, yani gcc'nin tersi. Bu yüzden iki derleyicide çıktılar farklı. Kod yazarken bu gibi belirsiz durumlardan kaçınmak gerekli. Derleyicinize "-Wall" parametresini verirseniz sizi uyaracaktır zaten.

Teşekkürler, bunun aynısını ben Kaan Aslan A'dan Z'ye C Klavuzunda okumuştum aslında yani şu parametrelerin sağdan solda doğru işlediğini ancak hiç aklıma gelmemişti. Birde şu Wall konusunu araştırıcam merak ettim bilmiyordum. Belki onuda eğitici bi konu olması açısından paylaşabilirim.

Alıntı yapılan: bugra9 - 25 Temmuz 2016 - 16:18:02
Olay ++ operatörünün işleyişinden kaynaklanmakta. Öncelikle bu operatörü fonksiyon gibi düşünebilirsin ve bu fonksiyonların nasıl çalıştığını yazayım.

++i sözde kod

function ikiArtiOnde(i) {
    i = i + 1
    return i
}


i++ sözde kod

function ikiArtiArkada(i) {
    temp = i
    i = i + 1
    return temp
}


Farkettiysen birisinde değeri arttırıp yeni halini döndürürken diğerinde değerini arttırıp eski halinin yedeğini döndürüyor.

Şimdi koduna bakalım.
printf("%d - %p\n%p",say,p,p++);
Burada bu fonksiyonun aldığı parametrelere dikkat edelim. İlk ve ikinci parametre direk alınıyor ama üçüncü parametre fonksiyon gibi çalıştırılarak alınıyor. Yani 3. parametre olarak p'nin eski değerinin yedeği alınıyor. Sonra da p değeri arttırılıyor.

Şimdi yazdırma kısmını düşünelim.
- İkinci değer direk alındığı ve p ile aynı olduğu için, 3. değer işlenirken de p'nin değeri bir arttırıldığı için otomatik p+1 i ekrana basıyor.
- Üçüncü değer ise p'nin eski yedeği olduğu ve p+1 işleminden etkilenmediği için ekrana p değerini basıyor.
Böylece sana azaltılmış gibi görünüyor, olay bundan ibaret.

Konusu açılmışken küçük bir şeyden bahsedeyim.
for(;; i++)
kullanımı yanlış olup doğrusu
for(;; ++i)
şeklindedir. For ile i değerinin sadece 1 arttırılması beklenmekte olup fonksiyonun ne döndüreceği önemli değildir. Bundan dolayı ++i, i++'ya göre daha hızlı çalıştığı için bunu kullanmak daha doğrudur.

Olayı (Her ikinizin) sayenizde kavradım. Aslında bilmem gereken küçük bir ayrıntı varmış. Böyle küçük küçük denemeler yaparak güzel şeyler öğreniliyor sizin gibi cevaplayanlar olmasa * bir şey öğrenemicez.

Birde şu for olayında ++i , i++ ne kadar hızlı yani gerçekten kayda değer bir hız söz konusumu yoksa cpu işlemleri için  kayda değer midir ? Tabii her ikiside programcı için değerli olmak zorundadır. Bu ek bilgi içinde teşekkürler ben hep alışkanlıktan i++ kullanırdım. İlk dili öğrenirken örnek kodlarda öyle yazarlardı.

Amenofis

Hız farkı yok, o söylentiler 30 yıl öncesinden kalma. Günümüzde derleyiciler inanamayacağınız kadar akıllı. Örnek olarak bir paylaşım yapayım.

Aşağıdaki koddan ne beklersiniz? scanf ile sayı alınacak, sonra döngü içinde sayı her adımda 1 arttırılacak ve döngüden çıkınca sonuç yazılacak değil mi?
#include <stdio.h>
int main()
{
   int a;
   scanf("%d", &a);
   for(int i = 0; i < 10; i++)
      ++a;
   printf("%d\n", a);
   return 0;
}

Bakalım derleyicimiz ne yapmış. main fonksiyonunun assembly çıktısını alıyoruz. Bizi ilgilendiren yerlere açıklama yazdım.
Disassembly of section .text:
0000000000000000 <main>:
#include <stdio.h>
int main()
{
   0:   a9bf7bfd    stp   x29, x30, [sp,#-16]!
   4:   910003fd    mov   x29, sp
   8:   d10043ff    sub   sp, sp, #0x10
   int a;
   scanf("%d", &a);
   c:   90000000    adrp   x0, 0 <main>
  10:   91000000    add   x0, x0, #0x0
  14:   d10013a1    sub   x1, x29, #0x4
  18:   94000000    bl   0 <__isoc99_scanf>    <-- scanf fonk. çağrısı
   
   for(int i = 0; i < 10; i++)
      ++a;
  1c:   b85fc3a8    ldur   w8, [x29,#-4]      <-- girdiğimiz sayı w8 register ında
   printf("%d\n", a);
  20:   90000000    adrp   x0, 0 <main>
  24:   91000000    add   x0, x0, #0x0
int main()
{
   int a;
   scanf("%d", &a);
   
   for(int i = 0; i < 10; i++)
  28:   11002901    add   w1, w8, #0xa    <-- sayıya 10 (0xa) ekliyor :)
      ++a;
  2c:   b81fc3a1    stur   w1, [x29,#-4]  <-- sonuç belleğe yazılıyor
   printf("%d\n", a);
  30:   94000000    bl   0 <printf>       <-- printf çağrısı
   
   return 0;
  34:   2a1f03e0    mov   w0, wzr
  38:   910003bf    mov   sp, x29
  3c:   a8c17bfd    ldp   x29, x30, [sp],#16
  40:   d65f03c0    ret


Görüldüğü gibi derleyici yazdığımız döngüyü tümüyle görmezden gelmiş ve basitçe sayıya 10 eklemiş.

Neof07

#5
Alıntı yapılan: Amenofis - 26 Temmuz 2016 - 17:03:47
Hız farkı yok, o söylentiler 30 yıl öncesinden kalma. Günümüzde derleyiciler inanamayacağınız kadar akıllı. Örnek olarak bir paylaşım yapayım.

Aşağıdaki koddan ne beklersiniz? scanf ile sayı alınacak, sonra döngü içinde sayı her adımda 1 arttırılacak ve döngüden çıkınca sonuç yazılacak değil mi?
#include <stdio.h>
int main()
{
   int a;
   scanf("%d", &a);
   for(int i = 0; i < 10; i++)
      ++a;
   printf("%d\n", a);
   return 0;
}

Bakalım derleyicimiz ne yapmış. main fonksiyonunun assembly çıktısını alıyoruz. Bizi ilgilendiren yerlere açıklama yazdım.
Disassembly of section .text:
0000000000000000 <main>:
#include <stdio.h>
int main()
{
   0:   a9bf7bfd    stp   x29, x30, [sp,#-16]!
   4:   910003fd    mov   x29, sp
   8:   d10043ff    sub   sp, sp, #0x10
   int a;
   scanf("%d", &a);
   c:   90000000    adrp   x0, 0 <main>
  10:   91000000    add   x0, x0, #0x0
  14:   d10013a1    sub   x1, x29, #0x4
  18:   94000000    bl   0 <__isoc99_scanf>    <-- scanf fonk. çağrısı
   
   for(int i = 0; i < 10; i++)
      ++a;
  1c:   b85fc3a8    ldur   w8, [x29,#-4]      <-- girdiğimiz sayı w8 register ında
   printf("%d\n", a);
  20:   90000000    adrp   x0, 0 <main>
  24:   91000000    add   x0, x0, #0x0
int main()
{
   int a;
   scanf("%d", &a);
   
   for(int i = 0; i < 10; i++)
  28:   11002901    add   w1, w8, #0xa    <-- sayıya 10 (0xa) ekliyor :)
      ++a;
  2c:   b81fc3a1    stur   w1, [x29,#-4]  <-- sonuç belleğe yazılıyor
   printf("%d\n", a);
  30:   94000000    bl   0 <printf>       <-- printf çağrısı
   
   return 0;
  34:   2a1f03e0    mov   w0, wzr
  38:   910003bf    mov   sp, x29
  3c:   a8c17bfd    ldp   x29, x30, [sp],#16
  40:   d65f03c0    ret


Görüldüğü gibi derleyici yazdığımız döngüyü tümüyle görmezden gelmiş ve basitçe sayıya 10 eklemiş.

asm çıktısı karışık baya :D Bu arada bu asm çıktısını nereden görebiliyoruz ? Normal gcc derleyicisi komut satırından derlerken nasıl bulabilirim o asm çıktısını peki ?

Fark yoksa sanırım sıkıntı çıkmayacaktır.

----/ Konumuzla alakası yok lakin size özel mesajdan driverlarla ilgili danışmak istediğim bir konu var eğer müsait olduğunuz zamanları yazarsanız ben size ulaşmaya çalışayım. Şu an mint yükledim ama debian kurmak istiyorum. Denedim ancak benim wirelles i ve ekran kartımı tanıtamadım. Debian forumlarına girdim sorunumu yazdım ilk baş modemden sorun var dediler, yok dedim ağ adaptörünü tanımıyor dedim. Ancak verdikleri linkleri denedim olmadı. İlla başta kabloyla bağlamak zorunda kalıyorum. Hadi onu geçtim kabloyla bağlasam bile yüklenen driverlar sonucu tanımıyor her iki donanımıda. Baya can sıkıcı bir durum. Özetle forumlarda bulamamıştım sorunu. Eğer çözebilirsem buradada paylaşırım herkes faydalansın bu konuda bir konu göremedim hiç..

bugra9

Alıntı YapBirde şu for olayında ++i , i++ ne kadar hızlı yani gerçekten kayda değer bir hız söz konusumu yoksa cpu işlemleri için  kayda değer midir ? Tabii her ikiside programcı için değerli olmak zorundadır.

İkisinin koduna bakıp gördüğün gibi arada 1 cpu birim fazla işlem var.  Tabi günümüzde işlemciler çok hızlı olup asıl yavaşlamaya sebep olan io birimlerindeki yanlış kullanımlar veya arama gibi kritik olaylar. Bir de @Amenofis'in değindiği gibi bazı derleyiciler bazı durumlarda senin kodunu düzeltebiliyor. Tabi burada dilin ve derleyicinin tüm bu davranışlarını ezberlemen gerekiyor ki sonradan derleyicin seni yarı yolda bırakmasın. Yine değindiği gibi başlangıç düzeyindeki yanlışların hemen hemen hepsini düzeltecek kadar akıllılar ama işler biraz daha ileri gitti mi tüm derleyicilerin çakıldığını düşünüyorum. Sonunda da her zaman konu olan optimizasyon sorunları meydana geliyor ki bu sorunlar 30 yıl önce de vardı, biz kod yazdığımız sürece devam edecek.

Sonuç olarak bu bir alışkanlık meselesidir, bir kere boş verdin mi devamı da gelecektir. Sonunda da kodların ya hep yavaş ya da yanlış çalışacaktır. Baştan her şeyin doğrusunu kullanmaya çalışmak daha sağlıklı değil mi?

---
@Neof07, birine seslenmek için şuan yaptığım kullanımı yapabilirsin. Kişinin tüm iletisini alıntılamak okumayı aşırı zorlaştırıyor. Alıntı, kişinin iletisinden sadece bir kısmına cevap vermek için kullanılır.

Bir de konu senin için çözüldüyse şöyle bir uygulamamız var.
https://forum.ubuntu-tr.net/index.php?topic=31789.0

Farklı konular için yeni konu başlatabilirsin.