सांख्यिकीय रूप से जुड़े पुस्तकालयों के बीच प्रतीक टकराव से कैसे निपटें?


82

पुस्तकालय लिखते समय सबसे महत्वपूर्ण नियमों और सर्वोत्तम प्रथाओं में से एक, पुस्तकालय के सभी प्रतीकों को एक पुस्तकालय विशिष्ट नामस्थान में डाल रहा है। namespaceकीवर्ड के कारण C ++ इसे आसान बनाता है । सी में सामान्य दृष्टिकोण कुछ पुस्तकालय विशिष्ट उपसर्ग के साथ पहचानकर्ताओं को उपसर्ग करना है।

सी मानक के नियमों ने उन पर कुछ अड़चनें डालीं (सुरक्षित संकलन के लिए): एसी कंपाइलर एक पहचानकर्ता के केवल पहले 8 अक्षरों को देख सकता है , इसलिए foobar2k_eggsऔर foobar2k_spamसमान पहचानकर्ताओं के रूप में व्याख्या की जा सकती है - हालाँकि हर आधुनिक कंपाइलर मनमाने ढंग से लंबे पहचानकर्ताओं की अनुमति देता है , इसलिए हमारे समय में (21 वीं सदी) हमें इस बारे में परेशान नहीं होना चाहिए।

लेकिन क्या होगा अगर आप कुछ पुस्तकालयों का सामना कर रहे हैं जिनमें से आप प्रतीक के नाम / आईडीफायर को बदल नहीं सकते हैं? हो सकता है कि आपको केवल एक स्थिर बाइनरी और हेडर मिले या न चाहते हुए भी, या खुद को समायोजित करने और पुन: व्यवस्थित करने की अनुमति न हो।


इन्हें भी देखें: stackoverflow.com/questions/6538501/…
ninjalj

जवाबों:


141

कम से कम स्थिर पुस्तकालयों के मामले में आप इसके चारों ओर काफी आसानी से काम कर सकते हैं।

पुस्तकालयों फू और बार के उन हेडर पर विचार करें । इस ट्यूटोरियल के लिए मैं आपको सोर्स फाइल्स भी दूंगा

उदाहरण / ex01 / foo.h

int spam(void);
double eggs(void);

उदाहरण / ex01 / foo.c (यह अपारदर्शी / उपलब्ध नहीं हो सकता है)

int the_spams;
double the_eggs;

int spam()
{
    return the_spams++;
}

double eggs()
{
    return the_eggs--;
}

उदाहरण / ex01 / bar.h

int spam(int new_spams);
double eggs(double new_eggs);

उदाहरण / ex01 / bar.c (यह अपारदर्शी / उपलब्ध नहीं हो सकता है)

int the_spams;
double the_eggs;

int spam(int new_spams)
{
    int old_spams = the_spams;
    the_spams = new_spams;
    return old_spams;
}

double eggs(double new_eggs)
{
    double old_eggs = the_eggs;
    the_eggs = new_eggs;
    return old_eggs;
}

हम एक प्रोग्राम फ़ॉबर में उन का उपयोग करना चाहते हैं

उदाहरण / ex01 / foobar.c

#include <stdio.h>

#include "foo.h"
#include "bar.h"

int main()
{
    const int    new_bar_spam = 3;
    const double new_bar_eggs = 5.0f;

    printf("foo: spam = %d, eggs = %f\n", spam(), eggs() );
    printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n", 
            spam(new_bar_spam), new_bar_spam, 
            eggs(new_bar_eggs), new_bar_eggs );

    return 0;
}

एक समस्या तुरंत स्पष्ट हो जाती है: सी ओवरलोडिंग नहीं जानता। इसलिए हमारे पास समान नाम के साथ दो बार दो कार्य हैं लेकिन विभिन्न हस्ताक्षर हैं। इसलिए हमें कुछ अलग करने की जरूरत है। वैसे भी, देखते हैं कि इस बारे में एक संकलक का क्या कहना है:

example/ex01/ $ make
cc    -c -o foobar.o foobar.c
In file included from foobar.c:4:
bar.h:1: error: conflicting types for ‘spam’
foo.h:1: note: previous declaration of ‘spam’ was here
bar.h:2: error: conflicting types for ‘eggs’
foo.h:2: note: previous declaration of ‘eggs’ was here
foobar.c: In function ‘main’:
foobar.c:11: error: too few arguments to function ‘spam’
foobar.c:11: error: too few arguments to function ‘eggs’
make: *** [foobar.o] Error 1

ठीक है, यह कोई आश्चर्य की बात नहीं थी, यह सिर्फ हमें बताया गया था, जिसे हम पहले से जानते थे, या कम से कम संदेह था।

तो क्या हम किसी तरह मूल पुस्तकालयों के स्रोत कोड या हेडर को संशोधित किए बिना उस पहचानकर्ता टकराव को हल कर सकते हैं? वास्तव में हम कर सकते हैं।

पहले संकलन समय समस्याओं को हल करने देता है। इसके लिए हम शीर्षलेख को घेरते हैं जिसमें प्रीप्रोसेसर #defineनिर्देशों का एक समूह शामिल है जो पुस्तकालय द्वारा निर्यात किए गए सभी प्रतीकों को उपसर्ग करता है। बाद में हम कुछ अच्छे आरामदायक आवरण-हेडर के साथ ऐसा करते हैं, लेकिन केवल यह प्रदर्शित करने के लिए कि फ़ोबार.सी . सी। स्रोत फ़ाइल में इसे क्या किया जा रहा है :

उदाहरण / ex02 / foobar.c

#include <stdio.h>

#define spam foo_spam
#define eggs foo_eggs
#  include "foo.h"
#undef spam
#undef eggs

#define spam bar_spam
#define eggs bar_eggs
#  include "bar.h"
#undef spam
#undef eggs

int main()
{
    const int    new_bar_spam = 3;
    const double new_bar_eggs = 5.0f;

    printf("foo: spam = %d, eggs = %f\n", foo_spam(), foo_eggs() );
    printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n", 
           bar_spam(new_bar_spam), new_bar_spam, 
           bar_eggs(new_bar_eggs), new_bar_eggs );

    return 0;
}

अब अगर हम इसे संकलित करते हैं ...

example/ex02/ $ make
cc    -c -o foobar.o foobar.c
cc   foobar.o foo.o bar.o   -o foobar
bar.o: In function `spam':
bar.c:(.text+0x0): multiple definition of `spam'
foo.o:foo.c:(.text+0x0): first defined here
bar.o: In function `eggs':
bar.c:(.text+0x1e): multiple definition of `eggs'
foo.o:foo.c:(.text+0x19): first defined here
foobar.o: In function `main':
foobar.c:(.text+0x1e): undefined reference to `foo_eggs'
foobar.c:(.text+0x28): undefined reference to `foo_spam'
foobar.c:(.text+0x4d): undefined reference to `bar_eggs'
foobar.c:(.text+0x5c): undefined reference to `bar_spam'
collect2: ld returned 1 exit status
make: *** [foobar] Error 1

... यह पहली बार लगता है कि चीजें बदतर हो गईं। लेकिन बारीकी से देखो: वास्तव में संकलन चरण ठीक चला गया। यह सिर्फ लिंकर है जो अब शिकायत कर रहा है कि प्रतीक टकरा रहे हैं और यह हमें उस स्थान (स्रोत फ़ाइल और रेखा) को बताता है जहां ऐसा होता है। और जैसा कि हम देख सकते हैं कि वे प्रतीक उपसर्ग हैं।

आइए एनएम उपयोगिता के साथ प्रतीक तालिकाओं पर एक नज़र डालें :

example/ex02/ $ nm foo.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams

example/ex02/ $ nm bar.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams

तो अब हम कुछ अपारदर्शी बाइनरी में उन प्रतीकों को उपसर्ग करने के लिए व्यायाम के साथ चुनौती दी है। हां, मुझे पता है कि इस उदाहरण के अनुसार हमारे पास स्रोत हैं और हम इसे बदल सकते हैं। लेकिन अभी के लिए, बस मान लें कि आपके पास केवल .o फाइलें, या a .a (जो वास्तव में सिर्फ .o का एक गुच्छा है )।

बचाव के लिए objcopy

हमारे लिए विशेष रूप से दिलचस्प एक उपकरण है: objcopy

objcopy अस्थायी फ़ाइलों पर काम करती है, इसलिए हम इसका उपयोग कर सकते हैं जैसे कि यह इन-प्लेस काम कर रहा हो। एक विकल्प / ऑपरेशन है - जिसे - उपसर्ग-प्रतीक कहा जाता है और आपके पास 3 अनुमान हैं कि यह क्या करता है।

तो आइए इस हलाला को हमारे जिद्दी पुस्तकालयों पर फेंक दें:

example/ex03/ $ objcopy --prefix-symbols=foo_ foo.o
example/ex03/ $ objcopy --prefix-symbols=bar_ bar.o

एनएम हमें पता चलता है कि यह काम करने के लिए लग रहा था:

example/ex03/ $ nm foo.o
0000000000000019 T foo_eggs
0000000000000000 T foo_spam
0000000000000008 C foo_the_eggs
0000000000000004 C foo_the_spams

example/ex03/ $ nm bar.o
000000000000001e T bar_eggs
0000000000000000 T bar_spam
0000000000000008 C bar_the_eggs
0000000000000004 C bar_the_spams

इस पूरी चीज़ को जोड़ने की कोशिश करें:

example/ex03/ $ make
cc   foobar.o foo.o bar.o   -o foobar

और वास्तव में, यह काम किया:

example/ex03/ $ ./foobar 
foo: spam = 0, eggs = 0.000000
bar: old spam = 0, new spam = 3 ; old eggs = 0.000000, new eggs = 5.000000

अब मैं इसे एक उपकरण / स्क्रिप्ट को लागू करने के लिए पाठक के लिए एक अभ्यास के रूप में छोड़ देता हूं जो स्वचालित रूप से एनएम का उपयोग करके पुस्तकालय के प्रतीकों को निकालता है , संरचना का एक आवरण हेडर फ़ाइल लिखता है

/* wrapper header wrapper_foo.h for foo.h */
#define spam foo_spam
#define eggs foo_eggs
/* ... */
#include <foo.h>
#undef spam
#undef eggs
/* ... */

और objcopy का उपयोग करके स्थिर लायब्रेरी की ऑब्जेक्ट फ़ाइलों के लिए प्रतीक उपसर्ग लागू करता है ।

साझा पुस्तकालयों के बारे में क्या?

सिद्धांत रूप में साझा पुस्तकालयों के साथ भी ऐसा ही किया जा सकता है। हालाँकि साझा लाइब्रेरी, यह नाम बताता है, कई कार्यक्रमों में साझा किया जाता है, इसलिए इस तरह से साझा लाइब्रेरी के साथ खिलवाड़ करना इतना अच्छा विचार नहीं है।

आपको एक ट्रैंपोलिन आवरण लिखने के आसपास नहीं मिलेगा। इससे भी बदतर आप ऑब्जेक्ट फ़ाइल स्तर पर साझा पुस्तकालय के खिलाफ लिंक नहीं कर सकते हैं, लेकिन गतिशील लोडिंग करने के लिए मजबूर हैं। लेकिन यह इसके अपने लेख के योग्य है।

देखते रहें, और खुश कोडिंग।


4
प्रभावशाली! उम्मीद नहीं थी कि यह इतना आसान होगा objcopy
कोस

12
क्या आपने अभी ... पूछने के 1 मिनट के भीतर अपने स्वयं के प्रश्न का उत्तर दिया?
एलेक्स बी

18
@ एलेक्स बी: यह एक ट्यूटोरियल आर्टिकल है और मैंने meta.stackoverflow.com पर मुझे सुझाए गए रास्ते का अनुसरण किया है कि कोई कैसे (दिलचस्प?) सवालों और उनके समाधानों के बारे में ट्यूटोरियल लगा सकता है। पुस्तकालयों को बंद करने के बारे में सवाल उठे और मैंने सोचा कि "एचएम, मुझे पता है कि इस तरह के समाधान से कैसे निपटना है", एक लेख लिखा और इसे प्रश्नोत्तर के रूप में यहां पोस्ट किया। meta.stackexchange.com/questions/97240/…
datenwolf

4
@datenwolf आईओएस पुस्तकालयों के लिए इस समस्या को हल करने के बारे में कोई भी विचार। जैसा कि मुझे पता चला, objcopy iOS पुस्तकालयों का समर्थन नहीं करता है: /
Ege Akpinar

6
ब्ला ब्ला ब्ला objcopy --prefix-symbols ... +1!
बेन जैक्सन

7

C मानक के नियमों ने उन पर कुछ अड़चनें डालीं (सुरक्षित संकलन के लिए): AC कंपाइलर किसी पहचानकर्ता के केवल पहले 8 अक्षरों को देख सकता है, इसलिए foobar2k_eggs और foobar2k_spam को समान पहचानकर्ता के रूप में मान्य किया जा सकता है - हालाँकि हर आधुनिक कंपाइलर मनमाना अनुमति देता है लंबे पहचानकर्ता, इसलिए हमारे समय (21 वीं सदी) में हमें इस बारे में परेशान नहीं होना चाहिए।

यह केवल आधुनिक संकलक का विस्तार नहीं है; वर्तमान सी मानक को भी लंबे समय तक बाहरी नामों का समर्थन करने के लिए संकलक की आवश्यकता होती है। मैं सही लंबाई भूल जाता हूं, लेकिन अगर मुझे सही याद है तो यह 31 अक्षरों जैसा है।

लेकिन क्या होगा अगर आप कुछ पुस्तकालयों का सामना कर रहे हैं जिनमें से आप प्रतीक के नाम / आईडीफायर को बदल नहीं सकते हैं? हो सकता है कि आपको केवल एक स्थिर बाइनरी और हेडर मिले या न चाहते हुए भी, या खुद को समायोजित करने और पुन: व्यवस्थित करने की अनुमति न हो।

फिर तुम फंस गए। पुस्तकालय के लेखक से शिकायत करें। मुझे एक बार ऐसी बग का सामना करना पड़ा जहां मेरे आवेदन के उपयोगकर्ता डेबियन के libSDLलिंकिंग के कारण डेबियन पर इसे बनाने में असमर्थ थे libsoundfile, जिसने (कम से कम उस समय) वैश्विक नाम स्थान को प्रदूषित किया जैसे कि dsp(मैं आपको नहीं बच्चा!)। मैंने डेबियन से शिकायत की, और उन्होंने अपने पैकेज तय किए और फिक्स अपस्ट्रीम भेजा, जहां मुझे लगता है कि इसे लागू किया गया था, क्योंकि मैंने समस्या के बारे में फिर कभी नहीं सुना।

मुझे वास्तव में लगता है कि यह सबसे अच्छा तरीका है, क्योंकि यह सभी के लिए समस्या को हल करता है । आपके द्वारा किया गया कोई भी स्थानीय हैक अगले दुर्भाग्यपूर्ण उपयोगकर्ता के लिए लाइब्रेरी में समस्या का सामना करने और फिर से लड़ने के लिए छोड़ देगा।

यदि आपको वास्तव में एक त्वरित फिक्स की आवश्यकता है, और आपके पास स्रोत है, तो आप -Dfoo=crappylib_foo -Dbar=crappylib_barइसे ठीक करने के लिए मेकफिल में आदि का एक गुच्छा जोड़ सकते हैं । यदि नहीं, objcopyतो आपके द्वारा पाए गए समाधान का उपयोग करें ।


आप निश्चित रूप से सही हैं, हालांकि कभी-कभी आपको एक गंदे हैक की आवश्यकता होती है, जैसे कि मैंने ऊपर दिखाया था। उदाहरण के लिए यदि आप कुछ विरासत पुस्तकालय के साथ फंस गए हैं जहां विक्रेता व्यवसाय या समान से बाहर चला गया है। मैंने विशेष रूप से स्थैतिक पुस्तकालयों के लिए इसे लिखा था ।
डेटेनवुल्फ

3

यदि आप GCC का उपयोग कर रहे हैं, तो -अलॉल्ड-मल्टीपल-डेफिनेशन लिंकर स्विच एक आसान डिबगिंग टूल है। यह लिंकर को पहली परिभाषा (और इसके बारे में रोना नहीं) का उपयोग करने में खोखला कर देता है। इसके बारे में यहाँ और अधिक ।

इसने मुझे विकास के दौरान मदद की है जब मेरे पास एक विक्रेता-आपूर्ति की गई लाइब्रेरी का स्रोत उपलब्ध है और किसी कारण या अन्य के लिए लाइब्रेरी फ़ंक्शन में ट्रेस करने की आवश्यकता है। स्विच आपको स्रोत फ़ाइल की एक स्थानीय प्रतिलिपि में संकलन और लिंक करने की अनुमति देता है और फिर भी अनमॉडिफाइड स्थिर विक्रेता लाइब्रेरी से लिंक करता है। एक बार डिस्कवरी पूरी होने के बाद मेक सिंबल से वापस स्विच करना न भूलें। जानबूझकर नाम अंतरिक्ष टकराव के साथ शिपिंग रिलीज कोड अनजाने नाम अंतरिक्ष टकराव सहित नुकसान के लिए प्रवण है ।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.