Kaynak Koddan Makine Koduna: Visual Studio 2026 ile C++ Fonksiyonunun Memory Layout’u ve Assembly Temsili

Bir C++ fonksiyonunu yazıp Build dediğinde, aslında iki ayrı “dünya” ortaya çıkar: biri bizim okuduğumuz kaynak kod; diğeri CPU’nun çalıştırdığı bayt dizisi. Bu yazıda, Visual Studio 2026 akışını baz alarak, bir fonksiyonun assembly temsiline ve bellek yerleşimine “gerçekten anlaşılır” şekilde bakacağız.

Bu yazıda ne öğreneceksin?

  • Bir C++ fonksiyonu derlenince .text içinde nasıl temsil edilir?
  • Assembly komutları ile makine kodu (baytlar) arasındaki ilişki.
  • Prolog/Epilog nedir, ne zaman görünür, ne zaman kaybolur?
  • Debug ve Release arasındaki farkı derleme argümanları ile kontrol etmek.
  • Visual Studio 2026’da Disassembly penceresi, assembly listing (.asm) ve temel doğrulama akışı.

Visual Studio 2026 ön koşulları

Bu makale “Visual Studio 2026 kullanıyorsun” varsayımıyla yazıldı. C++ tarafında en pratik kurulum, Desktop development with C++ workload’ünün yüklü olmasıdır. Böylece MSVC toolset, Windows SDK, dumpbin ve Developer Command Prompt/PowerShell gibi parçalar hazır gelir.

Not

Bu yazı x64 odaklıdır. x86’da calling convention ve register/stack düzeni farklıdır.

Örnek fonksiyon: derleyiciyi görünür kılmak

Derleyiciler çok agresif optimizasyon yapabilir: fonksiyonu inline eder, bazı hesapları yok sayar, hatta “gerekli değil” diye fonksiyonun kendisini bile görünmez hale getirir. Bu yüzden iki küçük “kilit” kullanacağız: __declspec(noinline) ve volatile.

Örnek C++ fonksiyon (görünür/disassembly dostu)
#include <cstdint>

__declspec(noinline)
int add_and_scale(int a, int b)
{
    volatile int sum = a + b;
    int scaled = sum * 3;
    return scaled - 5;
}

Derleme argümanları: Debug vs Release

“Aynı C++ kodu neden farklı assembly üretiyor?” sorusunun en büyük cevabı: derleme ayarları. Bu yüzden burada işi “derle-topla” mantığıyla netleştiriyoruz: Debug’da öğren, Release’de gerçeği gör.

Hedef Öneri Beklenen sonuç
Debug (okunabilir) /Od, /Zi, /Ob0, /FAs Daha uzun ama takip etmesi kolay disassembly; stack frame sık görünür.
Release (optimize) /O2, /Ob2, (opsiyonel) /GL + /LTCG Daha kısa ve agresif kod; inline artar, bazı fonksiyonlar “kaybolur”.
Komut satırı örneği (Debug’a yakın .asm üretimi)
:: Debug odaklı: optimize kapalı, inline kapalı, asm listing açık
cl /std:c++20 /Od /Zi /Ob0 /FAs /Fa:out_debug.asm main.cpp
Komut satırı örneği (Release’e yakın .asm üretimi)
:: Release odaklı: optimize açık, inline agresif, asm listing açık
cl /std:c++20 /O2 /Ob2 /FAs /Fa:out_release.asm main.cpp

VS 2026: Disassembly penceresi ve .asm çıktısı

Visual Studio 2026’da iki pratik yol var:

  1. Debugger ile anlık disassembly: Debug → Windows → Disassembly
  2. Derleme ile .asm listing üretmek: Project Properties altında Assembly Output / Listing seçenekleri veya doğrudan /FAs, /Fa.
Pratik öneri

Önce Debug ayarlarıyla Disassembly penceresinde “hikâyeyi” oku. Sonra Release’e geçip “derleyici bunu nasıl kısaltmış?” diye kıyasla. Böyle yaptığında disassembly bir anda daha anlaşılır hale gelir.

Windows x64 calling convention (register düzeni)

x64 Windows tarafında temel kural basittir:

  • 1. parametre: RCX
  • 2. parametre: RDX
  • 3. parametre: R8
  • 4. parametre: R9
  • Dönüş değeri: RAX

Bizim fonksiyon için bu şu demek: a çoğunlukla ECX, b çoğunlukla EDX, sonuç EAX üzerinden döner.

Assembly örnekleri: prolog/epilog neden var?

Prolog/Epilog dediğimiz kısım, fonksiyonun stack’te yer ayırması ve işi bitince toparlamasıdır. Debug’da sık görürsün; Release’de (özellikle “leaf function” ise) bazen hiç görünmez.

Release’e yakın sade akış (örnek disassembly)
add_and_scale:
  lea eax, [rcx + rdx]     ; eax = a + b
  imul eax, eax, 3             ; eax *= 3
  sub eax, 5                     ; eax -= 5
  ret
Debug/volatile etkisiyle stack frame’li akış (örnek disassembly)
add_and_scale:
  sub rsp, 40h                    ; stack alanı aç
  mov dword ptr [rsp+20h], ecx  ; sum = a
  add dword ptr [rsp+20h], edx  ; sum += b
  mov eax, dword ptr [rsp+20h]  ; eax = sum
  imul eax, eax, 3
  sub eax, 5
  add rsp, 40h
  ret

.text, RVA/VA ve ASLR: memory layout’u anlamak

“Memory layout” dediğimiz şeyin çok pratik bir özeti var: Fonksiyonun kodu PE içinde genellikle .text bölümündedir ve burada bir RVA ile temsil edilir. Çalışma anında ise Windows loader bu bölümü memory’ye map eder ve adres şöyle oluşur:

Aklında tut

VA = ModuleBase + RVA

ASLR açıksa ModuleBase değişebilir; bu yüzden “debugger’daki adres” her çalıştırmada oynayabilir. Ama aynı build için RVA çoğunlukla sabit kalır.

dumpbin ile doğrulama

Visual Studio 2026 tarafında “Developer Command Prompt/PowerShell” açıp dumpbin ile PE içini hızlıca kontrol edebilirsin. Bu, “dosya dünyası”ndan bakmanın en kısa yoludur.

dumpbin ile .text / header kontrolü
dumpbin /HEADERS your.exe
dumpbin ile disassembly
dumpbin /DISASM your.exe

“Benim çıktım neden farklı?”

Aynı C++ koduna bakıp “bende farklı çıktı” görmek çok normal. En sık sebepler:

  • Optimizasyon: /Od ile /O2 arasında uçurum var.
  • Inline: /Ob2 ile bazı fonksiyonlar çağrı olarak görünmez (içeri gömülür).
  • LTCG: /GL + /LTCG link aşamasında bile kodu değiştirir.
  • Toolset/derleyici sürümü: VS 2026’da toolset seçimi bile çıktıyı etkileyebilir.
  • “Gözlemlenebilirlik”: Side-effect yoksa derleyici hesapları sadeleştirir.

SSS

1) Disassembly penceresinde fonksiyonumu bulamıyorum. Neden?

Büyük ihtimalle inline olmuştur veya optimizasyon sonucu “gömülmüştür”. __declspec(noinline) kullanıp Debug’da /Ob0 ile tekrar dene.

2) Debug’da stack frame görüyorum, Release’de yok. Bu normal mi?

Evet. Release daha agresif optimize eder ve register kullanımını artırır. Özellikle “başka fonksiyon çağırmayan” küçük fonksiyonlar (leaf) stack kullanmadan bitebilir.

3) Adresler her çalıştırmada değişiyor. Bu bir problem mi?

Genelde ASLR yüzünden olur. VA değişebilir. Aynı build için RVA çoğunlukla sabit kalır. Bu yüzden “RVA mantığı” ile düşünmek daha sağlıklıdır.


Kaynaklar


İlginizi çekebilecek diğer konular

Yorum Bırakın

E-posta adresiniz yayınlanmayacaktır. Zorunlu alanlar * ile işaretlenmiştir