> Linux Source Codes : https://elixir.bootlin.com/linux/latest/source > POSIX : https://pubs.opengroup.org/onlinepubs/9699919799/ > UNIX/Linux Sistemler, C programlarının derlenerek çalıştırılması. >> Dünyanın en yaygın C derleyicilerinden bir tanesi "gcc" derleyicisidir. GNU projesi kapsamında geliştirilmiş olup, "GNU C Compiler" şeklinde ismi açılabilir. >> MacOS sistemlerde ise varsayılan derleyici olarak "clang" derleyicisi kullanılmaktadır. >> UNIX/Linux sistemlerde "clang" derleyicisini yüklemek için aşağıdaki komutu kullanabiliriz; "sudo apt-get install clang" >> Gerek "gcc" gerek "clang" derleyicilerinde yapacağımız komut satırı işlemleri birbirinden farklılık göstermemektedir. >> "gcc" derleyicisini kullanarak bir C kodunun derlenmesi; >>> Herhangi bir editör kullanılarak bir C programı yazılır ve ".c" uzantısı ile kaydedilir. Örneğin, dosyamızın ismi "sample.c" olsun. >>> Daha sonra "shell" programı üzerinden, ilgili C programını yazdığımız dizinin içerisine gireriz ve aşağıdaki komutu çalıştırırız; "gcc sample.c" Bu işlem sonrasında ilgili dosyamızda herhangi bir hata yok ise ÖNCE DERLENİR, SONRASINDA DA BAĞLAMA işlemi yapılarak "executable" bir dosya elde edilir. İş bu dosyaya herhangi bir isim vermediğimiz için ismi varsayılan olarak "a.out" olur. >>> GNU sistemlerde derleme sonrası oluşturulan "object file", başarılı bir bağlama aşamasından sonra silinir. >>> GNU tip sistemlerde "executable" dosyalar için özel bir dosya uzantısı mevcut değildir. Sadece biz isim vermediğimiz zaman oluşturulan "a.out" isimli dosya için ".out" uzantısı eklenir. >>> Oluşturulan "executable" dosyaya isim vermek için, yukarıda yazdığımız "shell" komutuna aşağıdaki seçeneği ilave etmeliyiz; "-o file_name" Böylelikle "sample.c" isimli dosyayı derlemek ve çalıştırılabilir dosyanın da ismini "my_file_name" yapmak istiyorsak aşağıdaki komutu "shell" programında çalıştırmamız gerekiyor; "gcc sample.c -o my_file_name" GENEL OLARAK OLUŞTURULAN "executable" DOSYALAR İÇİN BİR UZANTI GİRİLMESİ TAVSİYE EDİLMEZ. >>> Windows sistemlerde ortaya çıkan "executable" dosyayı çalıştırmak için dosyanın adını direkt olarak "shell" terminaline yazmamız yeterliyken, GNU sistemlerde ise önce "./" ifadesini devamında ise ilgili dosyanın adını yazmamız gerekiyor. Dolayısıyla GNU sistemlerde "sample" isimli bir dosyayı çalıştırmak için aşağıdaki komutu "shell" programından çalıştırmalıyız; "./sample" >>> "gcc" derleyicisindeki bazı seçenekler; >>>> "-Wall" seçeneği, derleyicinin bütün uyarıları vermesine olanak sağlar. >>> 64-bit sistemlerde 32-bit derleme yapabilmek için, öncelikle aşağıdaki komutu "shell" programına girerek bir paket yüklemesi yapmamız gerekmektedir; "sudo apt-get install g++-multilib libc6-dev-i386" Devamında ise "-m32" seçeneceğini yazacağımız komuta eklememiz gerekmektedir. Böylelikle komutumuzun son hali aşağıdaki gibi olacaktır; "gcc sample.c -o my_file_name -m32" Böylelikle, ortaya çıkan "executable" dosya 32-bit bir dosya olacaktır. >>> Derleme sonrasında Sembolik Makine Dili çıktısı da görmek için "-S" seçeneği kullanılır. Bu durumda ek olarak ortaya ".s" bir dosya daha çıkacaktır. İlaveten bu seçenek ile birlikte "-masm=intel" seçeneği de kullanılsın ki ortaya çıkan dosyanın dili daha anlaşılır olsun. >> "shell" programındaki bazı komutlar; >>> "-c" seçeneği, ilgili dosyanın sadece derlenmesi için kullanılmalıdır. Bu seçenek girildiğinde BAĞLAMA İŞLEMİ YAPILMAYACAKTIR. Bu işlem sonucunda, GNU sistemlerde meydana getirilen dosyanın uzantısı ".o" şeklinde fakat Windows sistemlerinde bu dosyalar ".obj" uzantısına sahiptirler. >>> "pwd" komutu, "print working directory" manasına gelmekte olup, o anki çalışma dizinini ekrana yazdıracaktır. >>> "ls" komutu, o an bulunduğumuz dizindeki dosyaların isimlerini ekrana yazdıracaktır. >>> "ls -l" komutu, o an bulunduğumuz dizindeki dosyaların özelliklerini ekrana yazdıracaktır. >> Windows işletim sistemi üzerinde koşarken, "bash" uygulamasını çalıştırdığımız zaman o anki çalışma dizinimiz aşağıdaki gibi olacaktır; "ahmopasha@DESKTOP-OHF04Q3:/mnt/c/Windows/system32$" Bu noktada "cd .." komutunu tekrar tekrar kullanarak, dizinimizi aşağıdaki hale getirmeliyiz; "ahmopasha@DESKTOP-OHF04Q3:/mnt/c" Daha sonra sırasıyla "cd Users", "cd ahmet" ve "cd Desktop" komutlarını kullanarak dizinimizi aşağıdaki hale getirmeliyiz; "ahmopasha@DESKTOP-OHF04Q3:/mnt/c/Users/ahmet/Desktop$" Artık Windows işletim sistemindeki masaüstündeyiz. >> "mkdir folder_name" komutu ile "folder_name" isminde yeni bir dizin oluştururuz. >> "touch file_name" komutu ile "file_name" isminde yeni bir dosya oluşturuyoruz. > Hatırlatıcı Notlar: >> Advanced Programming in the UNIX Environment, 3rd Edition by W. Stevens & Stephen Rago : Bu kursa takviye olacak bir kitap. >> C dilinde "const" bir nesnenin "update" edilmesi Tanımsız Davranış oluşturur. >> C dilinde bir fonksiyon esasında "char" türden parametreyi kullansa bile fonksiyonun bildirim ve imzasında "int" türden belirtmeliyiz. Bu bir zorunluluk değil ama iyi bir pratiktir. Böylesi durumlarda gelenek "int" kullanılması yönünde. Benzer şekilde "short" türü için de geçerlidir. >> "Copy on Right" : https://en.wikipedia.org/wiki/Copy-on-write >> GNU kütüphanesinde C standartlarında olmayan bir takım veri yapıları mevcuttur. >> "FIFO", "First In First Out" mottosunu güderken "Priority Queue" ise önceliği yüksek olanın ilk sırada olduğu sistemlerdir. >> "flexible array member": Bir yapının son elemanı bir diziyse, bu dizinin boyutunu belirtmek zorunda değiliz. Buradaki tek ön koşul, yapının son elemanının bir dizi olması. Tabii ilgili derleyicinin C99 standartlarını desteklemesi gerekmektedir. >> Bir şeyi aşağı doğru nasıl yuvarlarız? Bölümünden kalan rakam kendisinden çıkartılarak. Örneğin, 13 rakamını 10'a yuvarlamak için 13'ün 10'a bölümünden kalan rakamı 13'ten çıkartırız. >> "Senkronize" demek, ilgili fonksiyon geri döndüğünde bahsi geçen işi yapmış olması garantidir. Öte yandan "Asenkron" demek, ilgili fonksiyon geri döndüğünde bahsi geçen işi yapmış olması GARANTİ DEĞİLDİR. Yani iş başlamıştır ama bitmiş olmayabilir. >> "Unspecified" demek programın çökmeyeceğini fakat sonucun öyle de olabileceği veya böyle de olabileceğini belirtir. >> "Undefined" demek ise programın çökebileceğini de belirtmektedir. >> C dilinde "Integer Types" ve "Floating-Point Types" türlerinin birleşim kümesi "Aritmetik Tür" olarak geçmektedir. >> "restrict" göstericiler, C99 ile dile eklenmiştir. Göstericilere özel bir nitelemedir. Yani sadece göstericinin kendisi "restrict" olabilir ve şu manaya gelmektedir: İlgili göstericinin gösterdiği belleğe sadece o gösterici ile erişebiliriz. Başka göstericiler ile aynı belleğe erişmeyeceğiz. Eğer ilgili bellek alanlarına başka göstericiler erişirse, "Tanımsız Davranış" meydana gelecektir. "Pointer Alliasing" optimizasyonuna olanak sağlar. Daha çok birden fazla gösterici-tip argüman alan fonksiyon imzalarına karşımıza çıkar. Böylesi bir senaryoda ise bize anlatımak istenen şey şudur; bu iki adres çakışık olmayacak. Yani iş bu fonksiyonun iki parametresine aynı adresi geçmeyeceğiz veya bir dizinin başlangıç adreslerini geçtiğimiz zaman bu iki adres değeri başka dizilere ait olmalı. Aynı diziye ait farklı adres bilgilerini bile geçsek yine sorun olacaktır. Tabii bu kural ilgili fonksiyonun çağrısı sürdüğü müddetçe geçerlidir. İlgili fonksiyondan geri döndükten sonra bu kural ortadan kalkmaktadır. Yani "restrict" olarak nitelenen göstericinin ömrü bittikten sonra ilgili alana diğer göstericiler üzerinden erişebiliriz. >> Aşağıdaki kullanım biçimi C99 ile legal hale gelmiştir: * Örnek 1, #include struct INFO{ int a; float f; }info; int main() { info = (struct INFO){ 10, 10.01f }; printf("%d - %f\n", info.a, info.f); // OUTPUT => 10 - 10.010000 int *ptr; ptr = (int[]){ 10, 20, 30}; // ptr = (int[3]){ 10, 20, 30}; for(int i = 0; i < 3; ++i) printf("%d ", *ptr++); // OUTPUT => 10 20 30 puts(""); ptr = (int[5]){100, 150, [2]=31, 200, 300}; for(int i = 0; i < 5; ++i) printf("%d ", *ptr++); // OUTPUT => 100 150 31 200 300 return 0; } >> Bir fonksiyon yazarken, >>> Eğer fonksiyon başarısız olmuş ise başarısızlığın nedenini dış dünyaya bildirmeye çalışın. >>> Eğer fonksiyon başarısız olmuş ise programın o anki durumunun ilgili fonksiyon çağrısından evvelki haline geri getirmeye çalışın. Yani aslında o fonksiyon hiç çağrılmamış gibi olsun. Bu durum ise "strong guarantee" demektir. Eğer program stabil ise fakat fonksiyon çağrılmadan evvelki duruma da geri dönmemiş ise "basic guarantee" denmektedir. >> Türkçe'nin Etimoloji Sözlüğü: https://www.nisanyansozluk.com/ >> Bir C programını derlediğimizde "link" işlemi için bir "main" fonksiyonunun bulunması gerekmektedir. Aslında GNU'nun "linker" programının ismi "ld" isimli programdır. "gcc" zaten bu "ld linker" ını çalıştırmaktadır. Bir programın "link" edilebilmesi için aslında "main" fonksiyonunun bulunması gerekmez. "main" fonksiyonu "assembly" düzeyinde anlamlı geçerli anlamlı bir fonksiyon değildir. C için anlamlı bir fonksiyondur. Yani örneğin biz bir "assembly" programı yazarsak onu istediğimiz yerden çalışmaya başlatabiliriz. Bir dosya "executable" olarak "link" edilirken tek gerekli olan şey "entry point" denilen akışın başlatılacağı noktadır. "Entry point", "ld linker" ında "--entry" seçeneği ile belirtilmektedir. Biz bir C programını "gcc" ile derlediğimizde "gcc" aslında "ld linker" ını çağırırken ismine "start-up modüller" denilen bir grup modülü de link işlemine gizlice dahil etmektedir. Programın gerçek "entry point" i bu "start-up" modül içerisinde bir yerdedir. Aslında "main" fonksiyonunu bu "start-up" modül çağırmaktadır. Bu "start-up" modülün görevi birtakım hazırlık işlemlerini yapıp komut satırı argümanlarıyla "main" fonksiyonunu çağırmaktır. Zaten akış "main" fonksiyonunu bitirdiğinde yeniden "start-up" modüldeki koda döner ki orada "exit" yapılmıştır. "Start-up" modülün kodlarını şöyle süşünebilirsiniz: ... ... ... call main call exit O halde "link" aşamsında bu "start-up" modül katıldığı için aslında "main" isimli bir fonksiyon aranmaktadır. Yani "start-up" modül, "main" fonksiyonunu çağırmasaydı linker onu aramayacaktı. Biz aslında hiçbir kütüphaneyi "link" aşamasına dahil etmeden programın "entry-point" ini kendismiz belirleyerek akışı istediğimiz fonksiyondan başlatabiliriz. Tabii bu durumda sistem fonksiyonlarını bile sembolik makine dilinde ya da gcc'nin "inline" sembolik makine dilinde kendimizin yazması gerekecektir. Aşağıda böyle bir örnek verilmiştir. Buradaki programın isminin "x.c" olduğunu varsayalım. Bu programı aşağıdaki gibi derleyip link edebilirsiniz: $ gcc -c x.c $ ld -o x x.o --entry=foo Programın kodu da aşağıdaki gibi olabilir: #include #include #include ssize_t my_write(int fd, const void *buf, size_t size) { register int64_t rax __asm__ ("rax") = 1; register int rdi __asm__ ("rdi") = fd; register const void *rsi __asm__ ("rsi") = buf; register size_t rdx __asm__ ("rdx") = size; __asm__ __volatile__ ( "syscall" : "+r" (rax) : "r" (rdi), "r" (rsi), "r" (rdx) : "rcx", "r11", "memory" ); return rax; } void my_exit(int status) { __asm__ __volatile__ ( "movl $60, %%eax\n\t" "movl %0, %%edi\n\t" "syscall" : : "g" (status) : "eax", "edi", "cc" ); } void foo() { my_write(1, "this is a test\n", 15); my_exit(0); } >> "xlinux.nist.gov/dads/" isimli internet sitesi "Dictionary of Algorithms & Data Structures" için kullanılır. >> "foldoc.org" isimli internet sitesi de bilgisayar bilimlerinde sözlük olarak kullanılır. >> "libgen.rs" internet istesinden PDF dosyalarına ulaşabiliriz. >> "volatile" : "const" ve "volatile" niteleyicileri şu bağlamda birbiriyle eştir; "non-const" bir değişkenin adresini "const" bir değişkene atayamadığımız gibi, "non-volatile" bir değişkenin adresini de "volatile" bir değişkene atayamayız. Benzer şekilde nesnemiz gösterici ise hem kendisi hem de gösterdiği yer hem "const" hem de "volatile" olabilir. Buna "cv-qualifier" denir. "volatile" ise şu anlama gelir; bir nesne eğer "volatile" ise bir başka akış tarafından nesnenin değeri değiştirilebilir. Eğer bir göstericisi "volatile" ise, yani kendisi "volatile" değil sadece gösterdiği yer "volatile", her defasında gösterdiği yere gidip taze kopyasını almaktadır. >> "atomic_t" : O nesne üzerinde yapılacak işlemlerin tek bir makina komutu ile yapılmasını sağlatır. >> C99 ile dile eklenen "_Exit" fonksiyonu, "_exit" POSIX fonksiyonu ile birebir aynı işlevselliktedir. >> UNIX/Linux, macOS ve Windows sistemlerinde yalnızca "CR ('\r')" karakteri imleci bulunulan satırın başına geçirmektedir. Dolayısıyla bir yazının sonunda "CR/LF" çifti varsa yazının ekrana bastırılmasında bir sorun oluşmayacaktır. Çünkü önce "CR" karakteri imleci bulunulan satırın başına geçirecek sonra "LF" karakteri aşağı satırın başına geçirecektir. Böylece yazının sonunda "LF" karakterinin bulunmasıyla "CR/LF" karakterlerinin bulunması arasında bir fark oluşmayacaktır. >> Sanal Makineler Hk. : Bu kurstaki denemeler ekseriyetle sanal makinalar kullanılarak gerçekleştirildi. Bu makinalardan önemli olanları "VMware Player" ve "VirtualBox" isimli programlardır. Bu makinalardan, -> "VMware Player" : Bu programı çalıştırdığımızda, sanal makinanın "MAC Address" bilgisini öğrenmek için, "Virtual Machine Settings/Network Adapter/Advanced" ayarından sanal "ethernet" kartının "MAC" adresini görebiliriz. Buradan "MAC" adresini, "00-0c-29-76-3b-e8" şeklinde görebiliriz. "IP" adresini görmek için de ilgili komut satırı programından, "arp -a" komutunu çalıştırmalıyız. Bu komutun çıktısı aşağıdaki gibidir: Interface: 192.168.153.1 --- 0x10 Internet Address Physical Address Type 192.168.153.128 00-0c-29-76-3b-e8 dynamic 192.168.153.131 00-0c-29-76-3b-fc dynamic 192.168.153.255 ff-ff-ff-ff-ff-ff static 224.0.0.22 01-00-5e-00-00-16 static 224.0.0.251 01-00-5e-00-00-fb static 224.0.0.252 01-00-5e-00-00-fc static 239.255.255.250 01-00-5e-7f-ff-fa static 255.255.255.255 ff-ff-ff-ff-ff-ff static Buradan "00-0c-29-76-3b-e8" numaralı "MAC" adresine karşılık gelen "IP" adresinin "192.168.153.128" olduğunu görüyoruz.