कई C++ फ़ाइलों से मूल Mac एक्ज़ीक्यूटेबल बनाने के लिए, सभी आवश्यक इम्प्लीमेंटेशन फ़ाइलें clang++ को दें, या उन्हें ऑब्जेक्ट फ़ाइलों में संकलित करके एक साथ लिंक करें। इस मार्गदर्शिका में एक छोटा Terminal प्रोग्राम बनाया गया है और यह बताया गया है कि अनुपलब्ध परिभाषाओं का पता कैसे लगाएँ।
Mac के कंपाइलर टूल तैयार करें
यह तरीका उस छोटे C++ प्रोग्राम के लिए अपनाएँ जिसे आप स्थानीय रूप से Terminal में चलाना चाहते हैं। Apple, Xcode या अलग Command Line Tools पैकेज के ज़रिए कंपाइलर और macOS SDK उपलब्ध कराता है। अगर Xcode पहले से इंस्टॉल है, तो अलग पैकेज की ज़रूरत नहीं है। अन्यथा, नीचे दिया गया कमांड चलाएँ और इंस्टॉलेशन संवाद पूरा करें। Apple का कमांड-लाइन टूल इंस्टॉल करना देखें।
xcode-select --install
इंस्टॉलेशन के बाद जाँचें कि कंपाइलर उपलब्ध है:
clang++ --version
अंतिम लिंक के लिए clang++ का उपयोग करें, भले ही आपके इनपुट पहले से संकलित ऑब्जेक्ट फ़ाइलें हों। Clang का दस्तावेज़ C++ प्रोग्रामों के लिए इस ड्राइवर की स्पष्ट अनुशंसा करता है, क्योंकि भाषा मोड यह तय करता है कि कौन-सी रनटाइम लाइब्रेरी अपने आप लिंक होंगी। एक संपूर्ण टूलचेन तैयार करना देखें।
तीन फ़ाइलों वाला पूरा C++ प्रोग्राम बनाएँ
इस उदाहरण में तीन डेस्क ट्रे पर रखी चीज़ों की गिनती करके नतीजे को फ़ॉर्मैट किया गया है। इसमें एक एंट्री पॉइंट और दो सहायक इम्प्लीमेंटेशन फ़ाइलें हैं। एक नई, खाली कार्यशील डायरेक्टरी बनाएँ, फिर ये चारों फ़ाइलें सीधे उसके अंदर सहेजें:
desk-demo/
desk.hpp
main.cpp
label.cpp
totals.cpp
साझा घोषणाएँ desk.hpp में सहेजें:
#ifndef DESK_DEMO_DESK_HPP
#define DESK_DEMO_DESK_HPP
#include <string>
std::string make_label(int count);
int total_items(int trays, int per_tray);
#endif
एंट्री पॉइंट main.cpp में सहेजें:
#include "desk.hpp"
#include <iostream>
int main() {
const int count = total_items(3, 4);
std::cout << make_label(count) << '\n';
return 0;
}
फ़ॉर्मैटिंग इम्प्लीमेंटेशन label.cpp में सहेजें:
#include "desk.hpp"
std::string make_label(int count) {
return "Desk items: " + std::to_string(count);
}
गिनती का इम्प्लीमेंटेशन totals.cpp में सहेजें:
#include "desk.hpp"
int total_items(int trays, int per_tray) {
return trays * per_tray;
}
हर इम्प्लीमेंटेशन एक ही हेडर शामिल करता है, जिससे कंपाइलर उसके फ़ंक्शन की परिभाषा को साझा घोषणा के विरुद्ध जाँच सकता है। हेडर गार्ड एक ही ट्रांसलेशन यूनिट में बार-बार शामिल होने से रोकता है। इन सामान्य, नॉन-इनलाइन फ़ंक्शन बॉडी को उनकी इम्प्लीमेंटेशन फ़ाइलों में रखें; C++ अपने एक-परिभाषा नियम के ज़रिए पूरे प्रोग्राम में परिभाषाओं की आवश्यकताएँ तय करता है।
एक ही कमांड में संकलित और लिंक करें
चारों फ़ाइलों वाली डायरेक्टरी में Terminal खोलें और चलाएँ:
clang++ -std=c++17 main.cpp label.cpp totals.cpp -o desk-demo && ./desk-demo
अपेक्षित आउटपुट यह है:
Desk items: 12
कमांड में तीनों इम्प्लीमेंटेशन फ़ाइलों के नाम दिए गए हैं और -o से आउटपुट फ़ाइल का नाम तय किया गया है। इसमें प्रक्रिया रोकने वाला कोई विकल्प नहीं है, इसलिए Clang इनपुट को संकलित करके लिंकर चलाता है। स्पष्ट -std=c++17 कंपाइलर के डिफ़ॉल्ट पर निर्भर रहने के बजाय भाषा का संस्करण चुनता है। ये विकल्प Clang कंपाइलर कमांड गाइड में दर्ज हैं।
शेल एक्ज़ीक्यूटेबल तभी चलाता है जब बिल्ड सफल हो, क्योंकि कमांड && से जोड़े गए हैं। दोबारा बिल्ड करते समय यह अहम है: बिल्ड विफल होने पर पुराना एक्ज़ीक्यूटेबल गलती से चलाकर उसके आउटपुट को नए नतीजे के रूप में नहीं समझना चाहिए।
वाइल्डकार्ड का उपयोग कब उचित है?
इसी डायरेक्टरी के लिए, स्पष्ट स्रोत सूची के विकल्प के तौर पर यह कमांड इस्तेमाल किया जा सकता है:
clang++ -std=c++17 ./*.cpp -o desk-demo && ./desk-demo
शेल ./*.cpp को वर्तमान डायरेक्टरी में उससे मेल खाने वाली फ़ाइलों की सूची में बदलता है। यह अंदर की डायरेक्टरियों में नहीं खोजता और .cc या .cxx में खत्म होने वाली फ़ाइलें भी शामिल नहीं करता। स्पष्ट रूप से पथ लिखते समय, जिन पथों में स्पेस हों उन्हें उद्धरण चिह्नों में रखें।
वाइल्डकार्ड का उपयोग तभी करें जब उससे मेल खाने वाली हर फ़ाइल इसी एक्ज़ीक्यूटेबल का हिस्सा हो। बाद में अगर आप अपने अलग main वाले डेमो प्रोग्राम या किसी प्लेटफ़ॉर्म-विशिष्ट वैकल्पिक इम्प्लीमेंटेशन को जोड़ते हैं, तो स्पष्ट स्रोत सूची रखें। यहाँ “सभी इम्प्लीमेंटेशन फ़ाइलों” का अर्थ है किसी एक टार्गेट के लिए ज़रूरी सभी फ़ाइलें, जिनमें हर आवश्यक परिभाषा उचित तरीके से दी गई हो।
पहले ऑब्जेक्ट फ़ाइलें संकलित करें, फिर लिंक करें
इन चरणों को अलग रखने से यह पता लगाना आसान होता है कि विफलता किसी एक स्रोत फ़ाइल को संकलित करते समय हुई या प्रोग्राम को जोड़ते समय। उन्हीं चार फ़ाइलों और कार्यशील डायरेक्टरी का उपयोग करके चलाएँ:
(
set -e
mkdir -p build
clang++ -std=c++17 -c main.cpp -o build/main.o
clang++ -std=c++17 -c label.cpp -o build/label.o
clang++ -std=c++17 -c totals.cpp -o build/totals.o
clang++ build/main.o build/label.o build/totals.o -o build/desk-demo
./build/desk-demo
)
-c विकल्प लिंक किए बिना ऑब्जेक्ट फ़ाइल बनाता है। अंतिम कंपाइलर इनवोकेशन तीनों ऑब्जेक्ट लेता है और एक्ज़ीक्यूटेबल को लिंक करता है। यह Clang के दर्ज संकलन चरणों का पालन करता है। सबशेल का set -e इस सीधे क्रम को किसी कमांड के विफल होने पर रोक देता है।
यहाँ भी स्पष्ट ऑब्जेक्ट नाम इस्तेमाल करें। दोबारा इस्तेमाल की गई बिल्ड डायरेक्टरी के सभी ऑब्जेक्ट लिंक करने से गलती से प्रोग्राम के पुराने संस्करण की फ़ाइल शामिल हो सकती है। अलग-अलग फ़ोल्डरों की दो स्रोत फ़ाइलों का बेसनेम एक जैसा हो, तो दोनों को एक ही आउटपुट फ़ाइल में लिखने के बजाय उनके ऑब्जेक्ट के लिए अलग-अलग पथ दें।
अगर आप सिर्फ label.cpp बदलते हैं, तो उस ऑब्जेक्ट को फिर से संकलित करके लिंक कमांड दोहरा सकते हैं। अगर आप desk.hpp बदलते हैं, तो उसे शामिल करने वाली हर स्रोत फ़ाइल को फिर से बनाएँ। इस मैन्युअल तरीके में इन संबंधों का हिसाब आपको रखना पड़ता है; प्रोजेक्ट बड़ा होने पर बिल्ड सिस्टम अधिक व्यावहारिक होता है।
बिल्ड कमांड बदलने से पहले विफलता का पता लगाएँ
नीचे दिया गया क्रम इस उदाहरण पर संकलन के चरण और C++ परिभाषा के नियम लागू करता है। इंस्टॉल किए गए टूलचेन के अनुसार डायग्नोस्टिक का सटीक शब्दांकन अलग हो सकता है।
| लक्षण | पहले क्या जाँचें | अगला उचित कदम |
|---|---|---|
| हेडर अनुपलब्ध बताया गया है | include का नाम और हेडर का स्थान | दिखाई गई डायरेक्टरी संरचना बहाल करें या सही हेडर खोज डायरेक्टरी सेट करें। |
लिंकिंग के दौरान make_label या total_items अपरिभाषित है |
क्या उसका इम्प्लीमेंटेशन इनपुट में है | अनुपलब्ध .cpp फ़ाइल या उसका संकलित ऑब्जेक्ट लिंक में जोड़ें। |
| फ़ाइल शामिल करने के बावजूद फ़ंक्शन अपरिभाषित है | घोषणा और परिभाषा के सिग्नेचर | नेमस्पेस, पैरामीटर प्रकार और क्वालिफ़ायर की तुलना करें; इम्प्लीमेंटेशन में साझा हेडर शामिल करें। |
| डुप्लिकेट सिंबल दिखाई देते हैं | बार-बार दिए गए इम्प्लीमेंटेशन इनपुट | दोहराया गया ऑब्जेक्ट, शामिल किया गया .cpp या अनचाही वैकल्पिक परिभाषा हटाएँ। |
main अपरिभाषित या डुप्लिकेट है |
एंट्री-पॉइंट फ़ाइलें | इच्छित main.cpp को ठीक एक बार शामिल करें और अन्य प्रोग्रामों के एंट्री पॉइंट हटा दें। |
| C++ लाइब्रेरी सिंबल अपरिभाषित हैं | अंतिम लिंक ड्राइवर | clang++ का उपयोग करें ताकि C++ रनटाइम दिए जा सकें। |
उदाहरण के लिए, केवल main.cpp को संकलित करने पर कॉल स्वीकार हो सकते हैं, क्योंकि हेडर दोनों फ़ंक्शन घोषित करता है। फिर भी अंतिम लिंक के लिए उनकी परिभाषाएँ चाहिए। एक और हेडर शामिल करने से अनुपलब्ध इम्प्लीमेंटेशन ऑब्जेक्ट नहीं मिलता। इसकी मूल आवश्यकताएँ C++ के एक-परिभाषा नियम में समझाई गई हैं।
label.cpp को main.cpp के अंदर शामिल करके, साथ ही label.cpp को स्वतंत्र रूप से संकलित करके, अनुपलब्ध सिंबल ठीक करने की कोशिश न करें। इससे एक ही सामान्य फ़ंक्शन परिभाषा दो ट्रांसलेशन यूनिट के ज़रिए मिलती है। साझा हेडर और अलग इम्प्लीमेंटेशन इनपुट पहले से ही इच्छित संरचना देते हैं।
बिल्ड चलाए बिना टूल कमांड देखने के लिए Clang का दर्ज -### विकल्प इस्तेमाल करें। इसे उद्धरण चिह्नों में रखें ताकि शेल इसे ज्यों का त्यों पास करे:
clang++ '-###' -std=c++17 main.cpp label.cpp totals.cpp -o desk-demo
इससे ड्राइवर द्वारा चुने गए कंपाइलर और लिंकर इनवोकेशन दिखते हैं, जिससे इच्छित कमांड और टूलचेन द्वारा चलाए जाने वाले कमांड में अंतर समझने में मदद मिलती है। एक संपूर्ण टूलचेन तैयार करना देखें।
आर्किटेक्चर, प्लेटफ़ॉर्म और ABI को संगत रखें
सभी स्रोत फ़ाइलें जोड़ने से असंगत बाइनरी इनपुट ठीक नहीं हो सकते। स्थानीय बिल्ड के लिए, एक इंस्टॉल किए गए Apple टूलचेन से शुरू करें और प्रोग्राम के स्रोतों को एक साथ फिर से बनाएँ। पहले से बनी लाइब्रेरी जोड़ते समय, स्रोत सूची बदलने से पहले उनके समर्थित प्लेटफ़ॉर्म और आर्किटेक्चर जाँचें।
वैकल्पिक यूनिवर्सल बिल्ड के लिए, Apple हर आर्किटेक्चर के लिए अलग संकलन और लिंक करने, फिर तैयार एक्ज़ीक्यूटेबल को lipo से मिलाने का तरीका बताता है। दोनों टार्गेट को सपोर्ट करने वाले टूलचेन और SDK के साथ, इसी नमूने को इस तरह बनाया जा सकता है। नीचे दिए गए macOS 11 डिप्लॉयमेंट टार्गेट उदाहरण के लिए चुने गए हैं:
(
set -e
mkdir -p build
clang++ -std=c++17 -target arm64-apple-macos11 \
main.cpp label.cpp totals.cpp -o build/desk-arm64
clang++ -std=c++17 -target x86_64-apple-macos11 \
main.cpp label.cpp totals.cpp -o build/desk-x86_64
lipo -create build/desk-arm64 build/desk-x86_64 \
-output build/desk-universal
lipo -archs build/desk-universal
)
लिंक की गई हर निर्भरता को संबंधित आर्किटेक्चर बिल्ड के लिए संगत इनपुट देना होगा। सिर्फ़ इसलिए iOS बाइनरी macOS बाइनरी नहीं बन जाती कि दोनों arm64 का उपयोग करते हैं। Apple यूनिवर्सल macOS बाइनरी बनाना में आर्किटेक्चर स्लाइस, प्लेटफ़ॉर्म के अंतर और एक्ज़ीक्यूटेबल की जाँच समझाता है।
C++ भाषा का संस्करण बाइनरी संगतता, यानी ABI, से भी अलग है। केवल -std=c++17 का मेल यह साबित नहीं करता कि अलग-अलग बनाई गई लाइब्रेरी के लेआउट और कॉन्फ़िगरेशन संगत हैं। LLVM, ABI को प्रभावित करने वाले लाइब्रेरी कॉन्फ़िगरेशन फ़्लैग दर्ज करता है और कहता है कि उपयोगकर्ताओं को ये विक्रेता फ़्लैग स्वयं सेट नहीं करने चाहिए। इस काम के लिए टूलचेन का सामान्य लाइब्रेरी कॉन्फ़िगरेशन बनाए रखना व्यावहारिक विकल्प है। libc++ की ABI गारंटियाँ देखें।
Xcode या मिश्रित Swift प्रोजेक्ट में वही स्रोत सूची लागू करें
मौजूदा Xcode टार्गेट में, टार्गेट चुनें और Build Phases खोलें। उस एक्ज़ीक्यूटेबल के लिए ज़रूरी इम्प्लीमेंटेशन फ़ाइलें Compile Sources में जाँचें। कस्टम लाइब्रेरी निर्भरताएँ Link Binary with Libraries के ज़रिए जोड़ें। Apple किसी टार्गेट के बिल्ड फ़ेज़ को कस्टमाइज़ करना में इन अलग-अलग ज़िम्मेदारियों का दस्तावेज़ देता है।
इस उदाहरण के लिए स्रोत सूची में main.cpp, label.cpp और totals.cpp शामिल होने चाहिए। प्रोजेक्ट में हेडर रखने से इम्प्लीमेंटेशन को संकलित करने की जगह नहीं मिलती।
अगर आसपास का एप्लिकेशन Swift का उपयोग करता है, तो ऊपर दिए गए शुद्ध C++ कमांड केवल C++ प्रोग्राम को कवर करते हैं। Swift का दस्तावेज़ीकृत तरीका भाषाओं के बीच API उपलब्ध कराने के लिए C++ इंटरऑपरेबिलिटी सक्षम करना ज़रूरी बनाता है; सीमा के पार केवल समर्थित भाषा सुविधाएँ ही जा सकती हैं। Swift और C++ को मिलाना देखें।
मिश्रित Swift और C++ कोड में एक ही C++ स्टैंडर्ड लाइब्रेरी का उपयोग होना चाहिए; Apple प्लेटफ़ॉर्म पर Swift libc++ का उपयोग करता है। Swift इंटरऑपरेबिलिटी के लिए कंपाइलर और भाषा संस्करण की शर्तें भी दर्ज करता है, जिनमें C++14 या नया संस्करण शामिल है। किसी मौजूदा लाइब्रेरी को संगत निर्भरता मानने से पहले इन आवश्यकताओं की जाँच करें। C++ इंटरऑपरेबिलिटी की समर्थित सुविधाएँ और सीमाएँ देखें।
स्थानीय Terminal कार्य के लिए, तीन स्रोत फ़ाइलों वाली स्पष्ट बिल्ड से शुरुआत करें। अगर यह विफल हो, तो विफलता का चरण पहचानें, अनुपलब्ध या दोहराई गई परिभाषा जाँचें और बाइनरी इनपुट की संगतता सत्यापित करें। कमांड से अपेक्षित नतीजा मिलने पर, यह बड़ी बिल्ड में ले जाने के लिए एक ठोस स्रोत सूची देता है।
स्रोत
- कमांड-लाइन टूल इंस्टॉल करना — Apple
- clang — Clang C, C++ और Objective-C कंपाइलर — LLVM Project
- एक संपूर्ण टूलचेन तैयार करना — LLVM Project
- एक-परिभाषा नियम — C++ Working Draft
- यूनिवर्सल macOS बाइनरी बनाना — Apple
- libc++ की ABI गारंटियाँ — LLVM Project
- किसी टार्गेट के बिल्ड फ़ेज़ को कस्टमाइज़ करना — Apple
- Swift और C++ को मिलाना — Swift.org
- C++ इंटरऑपरेबिलिटी की समर्थित सुविधाएँ और सीमाएँ — Swift.org