Globali bir başlık dosyasında extern ile bildirin, tam olarak bir .cpp dosyasında tanımlayın ve bu dosyanın nesne dosyasını onu kullanan her dosyayla birlikte bağlayın. Bu kılavuz, eksiksiz macOS derlemesini, adres kontrolünü ve C++17 inline değişkenin ne zaman uygun olduğunu gösterir.
Başlık dosyasındaki bir global neden yinelenen tanımlara yol açar?
Küçük bir yerel Mac programında amaç, ayrı ayrı derlenen birkaç kaynak dosyanın aynı nesneyi okumasını ve değiştirmesini sağlamaktır. Önce bildirimi tanımdan ayırın: extern int itemCount; bir nesneyi bildirirken, int itemCount = 0; onu tanımlar. extern ifadesine extern int itemCount = 0; örneğindeki gibi bir başlatıcı eklemek de onu tanıma dönüştürür. C++ çalışma taslağı bu ayrımı Declarations and definitions bölümünde açıklar.
Ad alanı kapsamındaki sıradan bir tanımı içeren başlık dosyası, onu içeren her kaynak dosyaya bu tanımı ekleyebilir. Dış bağlantıya sahip, inline olmayan ve şablon olmayan bir değişkenin çeviri birimleri arasında birden fazla tanımının bulunması tek tanım kuralını ihlal eder. Bağlayıcı yinelenen simgeler bildirebilir; ancak dil, bu tür her ihlalin tanılanmasını zorunlu kılmaz. Çalışma taslağındaki One-definition rule bölümüne bakın.
Bir ekleme koruyucusu, tek bir çeviri birimi içinde tekrar eklemeyi önler. Ayrı ayrı derlenen kaynak dosyalarını koordine etmez. Bu nedenle koruyuculu bir başlık dosyası yine de nesne dosyaları arasında birden fazla tanıma yol açabilir. Clang'in compiler command guide belgesi, önişleme sırasında başlıkların genişletilmesini ve nesne dosyalarının daha sonraki bağlama aşamasını açıklar.
Mac'inizde paylaşılan global örneğini eksiksiz olarak derleyin
Xcode'u veya Apple'ın Xcode için Komut Satırı Araçları paketini kullanın. Apple, bağımsız paketi Xcode dışında geliştirme için bir seçenek olarak belgeliyor; tam Xcode kurulumu araçları zaten içerir. İkisi de yüklü değilse aşağıdaki komutu çalıştırıp kurulum iletişim kutusundaki adımları tamamlayın. Installing the command-line tools bölümüne bakın.
xcode-select --install
Derlemeden önce seçili geliştirici dizinini ve derleyiciyi kontrol edin:
xcode-select --print-path
xcrun clang++ --version
Apple'ın arşivlenmiş Building from the Command Line with Xcode FAQ belgesi hem seçili geliştirici dizininin incelenmesini hem de geliştirici araçlarını bulmak veya çalıştırmak için xcrun kullanımını açıklar. Bu örnek boyunca aynı seçili araç zincirini kullanın.
shared-global-demo adında yeni ve boş bir klasör oluşturun, düzenleyicinizde açın ve aşağıdaki dört dosyayı buraya kaydedin. Bu, tek iş parçacıklı, örnek amaçlı bir C++ programıdır: bir dosya globalin sahibi olur, başka bir dosya onu değiştirir ve giriş noktası hem değerini hem de kimliğini kontrol eder.
Nesneyi paylaşılan başlıkta bildirin
Bunu shared_state.hpp adıyla kaydedin:
#ifndef SHARED_STATE_HPP
#define SHARED_STATE_HPP
namespace demo {
extern int itemCount;
void addItem();
int* itemCountAddress();
}
#endif
Başlık dosyası, her kaynak dosyanın kullandığı bildirimi sağlar. Ad alanını ve türü tek bir paylaşılan başlıkta tutmak, zamanla farklılaşabilecek ayrı elle yazılmış bildirimleri sürdürme gereğini de ortadan kaldırır.
Nesneye tek bir tanım verin
Bunu shared_state.cpp adıyla kaydedin:
#include "shared_state.hpp"
namespace demo {
int itemCount = 0;
}
Bu, demo::itemCount için tek tanımdır. Başlık dosyasını tanımın bulunduğu kaynak dosyaya eklemek, derleyicinin tanımın bildirimle uyumlu olduğunu denetlemesini sağlar. Açık başlatıcı, başlangıç değerini belirgin kılar.
Başka bir çeviri biriminden değiştirin
Bunu worker.cpp adıyla kaydedin:
#include "shared_state.hpp"
namespace demo {
void addItem() {
++itemCount;
}
int* itemCountAddress() {
return &itemCount;
}
}
Bunu main.cpp adıyla kaydedin:
#include "shared_state.hpp"
#include <iostream>
int main() {
std::cout << "before: " << demo::itemCount << '\n';
demo::addItem();
const bool sameObject =
demo::itemCountAddress() == &demo::itemCount;
std::cout << "after: " << demo::itemCount << '\n';
std::cout << std::boolalpha
<< "same object: " << sameObject << '\n';
return (demo::itemCount == 1 && sameObject) ? 0 : 1;
}
Adres karşılaştırması, başarılı bağlamanın ötesinde yararlı bir kontrol sağlar: worker.cpp gördüğü adresi döndürür, main.cpp ise bunu kendi adresiyle karşılaştırır. Çıkış durumu, değiştirilen değeri de kontrol eder. Her iki kontrol de iddiaların etkin olmasına bağlı değildir.
Ayrı ayrı derleyin, ardından üç nesne dosyasının tümünü bağlayın
Terminal'de bu dört dosyanın bulunduğu klasöre geçin. Aşağıdaki eksiksiz derleme bloğunu çalıştırın:
(
set -e
xcrun clang++ -std=c++17 -Wall -Wextra -pedantic \
-c shared_state.cpp -o shared_state.o
xcrun clang++ -std=c++17 -Wall -Wextra -pedantic \
-c worker.cpp -o worker.o
xcrun clang++ -std=c++17 -Wall -Wextra -pedantic \
-c main.cpp -o main.o
xcrun clang++ shared_state.o worker.o main.o -o shared-demo
./shared-demo
)
Clang, -c seçeneğinin bağlama yapmadan bir nesne dosyası oluşturduğunu belgeler; aşama seçeneği belirtilmediğinde sürücü bağlama işlemini yapar. Açıkça belirtilen -std=c++17, dil kipini seçer. Clang compiler command guide bölümüne bakın. Alt kabuk, bir komut başarısız olduğunda durur ve böylece başarısız bir derlemenin ardından eski bir yürütülebilir dosyanın çalıştırılmasını önler.
Kaynak kodundan çıkarılan ve Mac'te çalıştırılarak kaydedilmemiş beklenen çıktı şöyledir:
before: 0
after: 1
same object: true
Araştırma ortamında Mac derleyicisi bulunmadığından bu örnek çalıştırılmak yerine incelendi. Üç derleme komutu, nesne dosyaları arasındaki sınırı özellikle görünür kılar: shared_state.o tanımı sağlar; worker.o ve main.o ise onu kullanır.
C++17 inline değişkeni ne zaman kullanmalısınız?
Değişkenin tanımının paylaşılan bir başlık dosyasında bulunması gerekiyorsa C++17 inline değişken kullanın. Clang, inline değişkenleri C++17 language features arasında listeler. Dil, dış bağlantıya sahip bir inline değişkenin birden fazla çeviri biriminde tanımlanabileceğini; buna rağmen tek adresi olan tek bir varlık olarak kalacağını belirtir. The inline specifier bölümüne bakın.
Aynı örnek için shared_state.hpp dosyasının tüm içeriğini bu sürümle değiştirin:
#ifndef SHARED_STATE_HPP
#define SHARED_STATE_HPP
namespace demo {
inline int itemCount = 0;
void addItem();
int* itemCountAddress();
}
#endif
worker.cpp ve main.cpp dosyalarını değiştirmeyin. Bu varyantın derlemesinden shared_state.cpp dosyasını çıkarın: artık ayrı bir tanıma gerek yoktur. Her iki kullanıcıyı yeniden derleyin ve yalnızca onların nesne dosyalarını bağlayın:
(
set -e
xcrun clang++ -std=c++17 -Wall -Wextra -pedantic \
-c worker.cpp -o worker.o
xcrun clang++ -std=c++17 -Wall -Wextra -pedantic \
-c main.cpp -o main.o
xcrun clang++ worker.o main.o -o shared-demo
./shared-demo
)
Bu varyantta da beklenen çıktı aynıdır. Bağlama komutuna eski shared_state.o dosyasını eklemeyin. Kullanılmayan nesne dosyalarını açık bağlama listesinin dışında tutmak, sahiplik değişikliğini netleştirir.
Inline tanımlar, belirteç dizilerinin ve ad aramalarının eşleşmesi dahil olmak üzere tek tanım kuralının tutarlılık koşullarını karşılamalıdır. Farklı kaynak dosyalarında farklı başlatıcılar üreten makrolardan kaçının. Başarılı bir bağlama, kurala uyulduğunu tek başına göstermez. One-definition rule bölümüne bakın.
| Gereksinim | Uygun yaklaşım | Temel koşul |
|---|---|---|
| Tek bir uygulama dosyası değiştirilebilir durumun sahibi | extern içeren başlık bildirimi; tek bir .cpp tanımı |
Sahip olan nesne dosyasını tam bir kez bağlayın |
| Paylaşılan tanımın başlık dosyasında bulunması gerekiyor | C++17 inline değişken | Değişkeni kullanan her çeviri birimi uyumlu bir dil kipini ve tutarlı bir tanımı kullanır |
| Her kaynak dosyası bilerek ayrı duruma sahip | Ad alanı kapsamındaki static veya adsız bir ad alanı |
Bunlar iç bağlantı oluşturur; dolayısıyla paylaşılan nesne gereksinimini karşılamaz |
İç bağlantı ayrımı, çalışma taslağındaki Program and linkage bölümüne dayanır. Bu küçük, çok dosyalı programda extern yaklaşımı sahipliğin incelenmesini özellikle kolaylaştırır.
Derleyici seçeneklerini değiştirmeden önce hatayı tanılayın
Sahiplik sorununu eksik bir derleme girdisinden ayırmak için şu sırayı izleyin. Kontroller, yukarıda kullanılan bildirim kurallarına ve derleme aşamalarına dayanır; tanı iletilerinin ifadesi araç zincirine göre değişebilir.
- Derleme başarısız olursa önce bildirimleri kontrol edin. Her kaynak dosyanın
shared_state.hppdosyasını içerdiğinden ve aynı ad alanını, türü ve yazımı kullandığından emin olun. Bağlama, bildirilmemiş bir adı veya çelişen bildirimleri düzeltemez. - Bağlayıcı yinelenen simge bildirirse tanımları ve bağlama girdilerini sayın. Başlık dosyasındaki bir başlatıcıyı, ikinci bir
.cpptanımını veya aynı sahip nesnesinin iki kez verilmesini arayın. Inline varyantında eski, inline olmayan tanımın bulunmadığını doğrulayın. - Bağlayıcı tanımsız simge bildirirse eksik sahibi bulun.
externsürümündeshared_state.cppdosyasının başarıyla derlendiğini veshared_state.odosyasının son bağlama komutunda yer aldığını doğrulayın. Tek başına bir bildirim tanım sağlamaz. - Bağlama başarılı olduğu hâlde kimlik kontrolü başarısız olursa bağlantıyı inceleyin. Ad alanı kapsamındaki
staticdeğişkenlerini veya adsız ad alanlarının içindeki tanımları arayın. Bunlar her çeviri birimine kendi nesnesini verir. - Bir başlık dosyasını veya sahiplik stratejisini değiştirdikten sonra etkilenen tüm nesneleri yeniden derleyin. Çağıranların ve tanımlayıcı dosyanın güncel bildirimleri kullanması için ilgili eksiksiz derleme bloğunu yeniden çalıştırın.
İki kestirme yol özellikle dikkat gerektirir. İlk olarak, başlık dosyasındaki globali ad alanı kapsamındaki static int itemCount = 0; ile değiştirmek, her çeviri biriminde ayrı nesneler oluşturur. İkinci olarak, ad alanı kapsamındaki sıradan bir const değişkeni genellikle iç bağlantıya sahiptir; açık bir extern bildirimi veya inline gibi bir istisna yoksa. Amaçlanan bağlantı türünü bilinçli olarak kullanın; ilgili istisnalar Program and linkage bölümünde açıklanır.
Dil bağlantısını da koruyun. extern "C", C dil bağlantısını belirtir; eksik tanımı sağlamaz veya yinelenen tanımlara izin vermez. Tamamı C++ olan bu örnek için varsayılan C++ bağlantısını koruyun. Çalışma taslağı, dil bağlantısını ve bunun uygulamaya özgü adlandırma ve çağrı kuralı özelliklerini Linkage specifications bölümünde açıklar.
Mac uygulamasında Swift de varsa neler değişir?
C++ depolama düzeni yine C++ uygulamasına aittir. Bir başlık dosyasını Swift'e aktarmak bir arayüz sağlar; derlemenin, inline olmayan bir globalin sahibi olan C++ uygulamasını da içermesi gerekir. Swift'in mixed-language project setup guide belgesi, desteklenen C++ API'lerinin framework genel başlıkları veya uygulama hedefinin köprüleme başlığı aracılığıyla nasıl sunulacağını açıklar.
Swift'in C++'ı çağırması gereken bir Xcode hedefi için belgelenmiş ayar C++ and Objective-C interoperability olup C++ / Objective-C++ olarak belirlenir. Bu ayarı tüketen hedef için etkinleştirin ve uygun başlık dosyasını sunun. Bu, globale tek bir tanım sağlama gereksiniminden ayrıdır. Setting Up Mixed-Language Swift and C++ Projects bölümüne bakın.
Belgelenen uyumluluk sınırlarını koruyun: Swift C++ birlikte çalışabilirliği Swift 5.9 veya daha yenisini gerektirir, Swift ve C++ kodları aynı C++ standart kitaplığını kullanmalıdır ve Apple platformları libc++ kullanır. Bu makaledeki inline değişkeni içeren bir başlık dosyasını aktarmak için içe aktarıcıda da C++ kaynaklarında da C++17 veya daha yeni bir sürüm seçilmelidir. Swift, dil standardı denetimlerini ve derleyici kısıtlamalarını Supported Features and Constraints of C++ Interoperability bölümünde belgeler.
Buradaki örnekler, tek bir yerel yürütülebilir dosyaya bağlanan ve ayrı ayrı derlenen C++ dosyalarını kapsar. Adres kontrolünü uygularken bu kapsamı koruyun; ayrı yüklenen framework ve kitaplıklar için kendi dışa aktarma ve bağlama tasarımları gerekir. Gösterilen görev için tanımın sahibini belirleyin, bağlama işlemine bir kez ekleyin ve her iki çağıranın da aynı adresi ve güncellenmiş değeri gördüğünü doğrulayın.
Kaynaklar
- Installing the command-line tools — Apple.
- Building from the Command Line with Xcode FAQ — Apple Documentation Archive.
- clang — the Clang C, C++, and Objective-C compiler — Clang/LLVM.
- C++ Support in Clang — Clang/LLVM.
- Declarations and definitions — eel.is tarafından sunulan C++ çalışma taslağı.
- One-definition rule — eel.is tarafından sunulan C++ çalışma taslağı.
- The inline specifier — eel.is tarafından sunulan C++ çalışma taslağı.
- Program and linkage — eel.is tarafından sunulan C++ çalışma taslağı.
- Linkage specifications — eel.is tarafından sunulan C++ çalışma taslağı.
- Setting Up Mixed-Language Swift and C++ Projects — Swift.org.
- Supported Features and Constraints of C++ Interoperability — Swift.org.