__Attribute __ ((yapıcı)) tam olarak nasıl çalışır?


348

Bir şeyler kurması gerektiği oldukça açık görünüyor.

  1. Tam olarak ne zaman çalışır?
  2. Neden iki parantez var?
  3. __attribute__bir işlev? Makro mu? Sözdizimi?
  4. Bu C de çalışıyor mu? C ++?
  5. Çalıştığı işlevin statik olması gerekiyor mu?
  6. Ne zaman __attribute__((destructor))kaçıyor?

Objective-C Örneği :

__attribute__((constructor))
static void initialize_navigationBarImages() {
  navigationBarImages = [[NSMutableDictionary alloc] init];
}

__attribute__((destructor))
static void destroy_navigationBarImages() {
  [navigationBarImages release];
}

Yanıtlar:


273
  1. Paylaşılan bir kitaplık yüklendiğinde, genellikle program başlatılırken çalışır.
  2. Tüm GCC özellikleri bu şekilde; muhtemelen bunları işlev çağrılarından ayırt etmek.
  3. GCC'ye özel sözdizimi.
  4. Evet, bu C ve C ++ ile çalışır.
  5. Hayır, işlevin statik olması gerekmez.
  6. Yıkıcı, paylaşılan kitaplık kaldırıldığında, genellikle program çıkışında çalışır.

Böylece, yapıcıların ve yıkıcıların çalışma şekli, paylaşılan nesne dosyasının, sırasıyla yapıcı ve yıkıcı nitelikleriyle işaretlenmiş işlevlere referanslar içeren özel bölümler (ELF üzerinde .ctors ve .dtors) içermesidir. Kütüphane yüklendiğinde / kaldırıldığında, dinamik yükleyici programı (ld.so veya somesuch) bu bölümlerin var olup olmadığını kontrol eder ve eğer öyleyse burada atıfta bulunulan işlevleri çağırır.

Bunu düşünmeye gel, muhtemelen normal statik bağlayıcıda benzer bir sihir vardır, böylece kullanıcı statik veya dinamik bağlantı seçerse de aynı kod başlatma / kapatma sırasında çalıştırılır.


49
Çift parantez sayesinde "makro çıkışı" ( #define __attribute__(x)) kolayca yapılabilir . Birden fazla özelliğiniz varsa, örneğin, __attribute__((noreturn, weak))yalnızca bir köşeli parantez kümesi olsaydı "makro çıkışı" zor olurdu.
Chris Jester-Young

7
İle bitmedi .init/.fini. (Geçerli olarak tek bir çeviri biriminde birden çok kurucuya ve yıkıcıya sahip olabilirsiniz, nevermind tek bir kütüphanede olabilir - bu nasıl çalışır?) Bunun yerine, ELF ikili biçimini (Linux vb.) Kullanan platformlarda, kuruculara ve yıkıcılara başvurulur içinde .ctorsve .dtorsbaşlığının bölümleri. Doğru, eski günlerde, adlandırılmış initve var finiise dinamik kütüphane yükü ve boşaltma üzerinde çalıştırılacak olan işlevler , ancak şimdi bu daha iyi bir mekanizma ile değiştirildi.
ephemient

7
@jcayzac Hayır, çünkü variadic makrolar bir gcc uzantısıdır ve makrolaştırmanın ana nedeni, __attribute__gcc kullanmamanızdır, çünkü bu da bir gcc uzantısıdır.
Chris Jester-Young

9
@ ChrisJester-Young varyasyon makroları, bir GNU uzantısı değil, standart bir C99 özelliğidir.
jcayzac

4
'yapılmış "yerine' marka "şimdiki zaman (kullanımınız" - çift parens hala makro çıkarmanız kolay bunları yapmak Yanlış bilgiçlik ağaca havladı..
Jim BALTER

64

.init/ .finikullanımdan kaldırılmadı. Hala ELF standardının bir parçası ve sonsuza kadar olacağını söyleyebilirim. Kodu .init/ .finikod yüklenir zaman boş / yükleyici / çalışma bağlayıcı tarafından çalıştırılır. Yani her bir ELF yükü (örneğin, paylaşılan bir kütüphane) kodu .initçalıştırılacaktır. Hala aynı şeyi başarmak için bu mekanizmayı kullanmak hala mümkündür __attribute__((constructor))/((destructor)). Eski okul ama bazı faydaları var.

.ctors/ .dtorsMekanizması örneğin system-rtl / loader / linker-script desteği gerektirir. Bu, tüm sistemlerde mevcut olmaktan çok uzaktır, örneğin kodun çıplak metal üzerinde yürütüldüğü derin gömülü sistemler. Yani __attribute__((constructor))/((destructor)), GCC tarafından desteklense bile , onu düzenlemek için bağlayıcıya ve çalıştırmak için yükleyiciye (veya bazı durumlarda önyükleme koduna) bağlı olacağı kesin değildir. Kullanım için .init/ .finiyerine, kolay yolu bağlayıcı bayraklarını kullanmaktır: -init & -fini (GCC komut satırından yani sözdizimi olacaktır -Wl -init my_init -fini my_fini).

Her iki yöntemi de destekleyen sistemde, olası bir fayda, kodun .initönce çalıştırılması .ctorsve kodun .finisonra çalıştırılmasıdır .dtors. Sipariş alakalı ise, başlangıç ​​/ çıkış işlevlerini ayırt etmenin en az bir ham ama kolay yoludur.

En büyük dezavantaj, her yüklenebilir modül başına birden fazla _initve bir _finiişleve kolayca sahip olamamanız ve muhtemelen kodu .somotive edilenden daha fazla parçalamanız gerekmesidir. Bir diğeri, yukarıda açıklanan bağlayıcı yöntemini kullanırken, orijinal _init ve _finivarsayılan işlevlerin (tarafından sağlanan crti.o) değiştirilmesidir. Burası genellikle her türlü başlatma işleminin gerçekleştiği yerdir (Linux'ta bu, global değişken atamanın başlatıldığı yerdir). Burada anlatılan bir yol

Yukarıdaki bağlantıda _init(), hala yerinde olduğu için orijinaline basamaklı bir şeye ihtiyaç duyulmadığına dikkat edin. callMontaj ancak çizgi içi x86 anımsatıcı ve montaj (örneğin ARM gibi) birçok başka mimariler için tamamen farklı olmazdı bir işlevi çağırmak olduğunu. Yani kod şeffaf değil.

.init/ .finive .ctors/ .detorsmekanizmaları benzerdir, ancak tam olarak değildir. Kod .init/ .finiishal "olduğu gibi". Yani .init/ içinde birkaç fonksiyona sahip olabilirsiniz .fini, ancak AFAIK'i, birçok küçük .sodosyada kodu bozmadan tamamen şeffaf olarak saf C'ye koymak onları sözdizimsel olarak zordur .

.ctors/ .dtorsFarklı bir şekilde düzenlenir .init/ ' .fini. .ctors/ .dtorsbölümlerinin her ikisi de yalnızca işlevlere işaret eden tablolardır ve "arayan" her işlevi dolaylı olarak çağıran sistem tarafından sağlanan bir döngüdür. Yani döngü arayan mimariye özgü olabilir, ancak sistemin bir parçası olduğu için (eğer varsa) önemli değil.

Aşağıdaki snippet, .ctorsişlev dizisine temelde olduğu gibi yeni işlev işaretçileri ekler __attribute__((constructor))(yöntem birlikte bulunabilir) __attribute__((constructor))).

#define SECTION( S ) __attribute__ ((section ( S )))
void test(void) {
   printf("Hello\n");
}
void (*funcptr)(void) SECTION(".ctors") =test;
void (*funcptr2)(void) SECTION(".ctors") =test;
void (*funcptr3)(void) SECTION(".dtors") =test;

Tamamen farklı bir kendi icat ettiği bölüme fonksiyon göstergeleri de eklenebilir. Bu durumda değiştirilmiş bir bağlayıcı komut dosyası ve yükleyiciyi .ctors/ .dtorsdöngüyü taklit eden ek bir işlev gereklidir. Ancak bununla birlikte, yürütme sırası üzerinde daha iyi bir kontrol elde edilebilir, bağımsız değişken ve dönüş kodu işleme eta eklenebilir (Örneğin, bir C ++ projesinde, global kuruculardan önce veya sonra çalışan bir şeye ihtiyaç duymanız faydalı olacaktır).

__attribute__((constructor))/((destructor))Mümkün olduğunca tercih ederim , hile gibi hissetse bile basit ve zarif bir çözüm. Benim gibi çıplak metal kodlayıcılar için, bu her zaman bir seçenek değildir.

Bağlayıcılar ve yükleyiciler kitabında bazı iyi referans .


Yükleyici bu fonksiyonları nasıl çağırabilir? bu işlevler işlem adres alanında genelleri ve diğer işlevleri kullanabilir, ancak yükleyici kendi adres alanına sahip bir işlemdir, değil mi?
user2162550

@ user2162550 Hayır, ld-linux.so.2 (her zamanki "yorumlayıcı", dinamik olarak bağlı tüm yürütülebilir dosyalarda çalışan dinamik kitaplıklar için yükleyici) yürütülebilir dosyanın adres alanında çalışır. Genel olarak, dinamik kitaplık yükleyicinin kendisi, bir kitaplık kaynağına erişmeye çalışan iş parçacığı bağlamında çalışan, kullanıcı alanına özgü bir şeydir.
Paul Stelian

__attribute__((constructor))/((destructor))Yıkıcı olan koddan execv () çağırdığınızda çalışmaz. Yukarıda gösterildiği gibi .dtor'a giriş eklemek gibi birkaç şey denedim. Ama başarı yok. Kod numactl ile çalıştırarak sorunu çoğaltmak kolaydır. Örneğin, test_code'un yıkıcıyı içerdiğini varsayın (sorunu ayıklamak için yapıcıya ve yapıcı işlevlerine bir printf ekleyin). Sonra koş LD_PRELOAD=./test_code numactl -N 0 sleep 1. Yapıcıya iki kez ama yıkıcı sadece bir kez çağrıldığını göreceksiniz.
B Abali

39

Bu sayfa, ELF içindeki ve çalışmalarına izin veren bölümler constructorve destructoröznitelik uygulaması hakkında büyük bir anlayış sağlar . Burada sağlanan bilgileri sindirdikten sonra, biraz ek bilgi derledim ve (yukarıdaki Michael Ambrus'tan bölüm örneğini ödünç aldım) kavramları göstermek ve öğrenmeme yardımcı olmak için bir örnek oluşturdum. Bu sonuçlar aşağıda örnek kaynak ile birlikte verilmektedir.

Bu iş parçacığında açıklandığı gibi, constructorve destructoröznitelikleri nesne dosyasının .ctorsve .dtorsbölümünde girişler oluşturur . Her iki bölümdeki işlevlere üç yoldan biriyle başvuruda bulunabilirsiniz. (1) bu sectionözelliklerden herhangi birinin kullanılması ; (2) constructorve destructor(3) satır içi montaj çağrısı ile öznitelikler (Ambrus'un cevabındaki bağlantıya atıfta bulunularak).

constructorVe destructorözniteliklerinin kullanımı main(), çağrılmadan önce veya döndükten sonra yürütme sırasını denetlemek için yapıcıya / yıkıcıya ek olarak bir öncelik atamanıza izin verir . Verilen öncelik değeri ne kadar düşük olursa, yürütme önceliği o kadar yüksek olur (main () - öncesi yüksek önceliklerden önce ve main () sonrasındaki yüksek önceliklerden sonra düşük öncelikler yürütülür). Verdiğiniz öncelik değerleri , derleyici uygulama için 0-100 arasında öncelik değerleri ayırdığından daha büyük olmalıdır100 . A constructorveya destructoröncelikli olarak belirtilen bir önce constructorveya bir önceliksiz olarak destructorbelirtilir.

'Bölümünde' özellik ya da inline-montaj ile şunları yapabilirsiniz da yer fonksiyon referansları .initve .finisırasıyla her yıkıcının herhangi yapıcı önce ve sonra çalıştırır ELF kod bölümünde. Bölüme yerleştirilen işlev başvurusu tarafından çağrılan işlevler .init, işlev başvurusunun kendisinden önce (her zamanki gibi) yürütülür.

Aşağıdaki örnekte her birini göstermeye çalıştım:

#include <stdio.h>
#include <stdlib.h>

/*  test function utilizing attribute 'section' ".ctors"/".dtors"
    to create constuctors/destructors without assigned priority.
    (provided by Michael Ambrus in earlier answer)
*/

#define SECTION( S ) __attribute__ ((section ( S )))

void test (void) {
printf("\n\ttest() utilizing -- (.section .ctors/.dtors) w/o priority\n");
}

void (*funcptr1)(void) SECTION(".ctors") =test;
void (*funcptr2)(void) SECTION(".ctors") =test;
void (*funcptr3)(void) SECTION(".dtors") =test;

/*  functions constructX, destructX use attributes 'constructor' and
    'destructor' to create prioritized entries in the .ctors, .dtors
    ELF sections, respectively.

    NOTE: priorities 0-100 are reserved
*/
void construct1 () __attribute__ ((constructor (101)));
void construct2 () __attribute__ ((constructor (102)));
void destruct1 () __attribute__ ((destructor (101)));
void destruct2 () __attribute__ ((destructor (102)));

/*  init_some_function() - called by elf_init()
*/
int init_some_function () {
    printf ("\n  init_some_function() called by elf_init()\n");
    return 1;
}

/*  elf_init uses inline-assembly to place itself in the ELF .init section.
*/
int elf_init (void)
{
    __asm__ (".section .init \n call elf_init \n .section .text\n");

    if(!init_some_function ())
    {
        exit (1);
    }

    printf ("\n    elf_init() -- (.section .init)\n");

    return 1;
}

/*
    function definitions for constructX and destructX
*/
void construct1 () {
    printf ("\n      construct1() constructor -- (.section .ctors) priority 101\n");
}

void construct2 () {
    printf ("\n      construct2() constructor -- (.section .ctors) priority 102\n");
}

void destruct1 () {
    printf ("\n      destruct1() destructor -- (.section .dtors) priority 101\n\n");
}

void destruct2 () {
    printf ("\n      destruct2() destructor -- (.section .dtors) priority 102\n");
}

/* main makes no function call to any of the functions declared above
*/
int
main (int argc, char *argv[]) {

    printf ("\n\t  [ main body of program ]\n");

    return 0;
}

çıktı:

init_some_function() called by elf_init()

    elf_init() -- (.section .init)

    construct1() constructor -- (.section .ctors) priority 101

    construct2() constructor -- (.section .ctors) priority 102

        test() utilizing -- (.section .ctors/.dtors) w/o priority

        test() utilizing -- (.section .ctors/.dtors) w/o priority

        [ main body of program ]

        test() utilizing -- (.section .ctors/.dtors) w/o priority

    destruct2() destructor -- (.section .dtors) priority 102

    destruct1() destructor -- (.section .dtors) priority 101

Örnek, kurucu / yıkıcı davranışını güçlendirmeye yardımcı oldu, umarım başkaları için de yararlı olacaktır.


"Verdiğiniz öncelik değerlerinin 100'den büyük olması gerektiğini" nereden buldunuz? Bu bilgiler GCC işlev öznitelikleri belgelerinde yoktur.
Justin

4
IIRC, birkaç başvuru vardı, PATCH: Yapıcı / yıkıcı argümanları ( MAX_RESERVED_INIT_PRIORITY) için destek öncelik argümanı ve C ++ ( init_priority) 7.7 C ++ - Spesifik Değişken, Fonksiyon ve Tip Nitelikleri ile aynı olduklarını . Sonra bunu denedim 99: warning: constructor priorities from 0 to 100 are reserved for the implementation [enabled by default] void construct0 () __attribute__ ((constructor (99)));.
David C. Rankin

1
Ah. Clang ile <100 öncelikleri denedim ve çalışıyor gibi görünüyordu, ancak basit test durumum (tek bir derleme birimi) çok basitti .
Justin

1
Statik global değişkenlerin önceliği nedir (statik ctorlar)?
dashesy

2
Statik bir globalin etkisi ve görünürlüğü, programınızın nasıl yapılandırıldığına (örn. Tek dosya, birden çok dosya ( çeviri birimleri )) ve globalin bildirildiği şekle bağlıdır. Bkz: Statik (anahtar kelime) , özellikle Statik global değişken açıklaması.
David C. Rankin

7

İşte bu kullanışlı ama çirkin yapıların nasıl, neden ve ne zaman kullanılacağına dair "somut" (ve muhtemelen kullanışlı ) bir örnek ...

Xcode karar vermek "global" "kullanıcı varsayılan" kullanan XCTestObserversınıf dioksit kusuyor 's kalp için kuşatılmış konsoluna.

Bu örnekte ... bu psuedo kütüphanesini örtük olarak yüklediğimde, diyelim ki ... libdemure.a, test hedefimdeki bir bayrakla ..

OTHER_LDFLAGS = -ldemure

İstiyorum..

  1. Yükte (yani XCTesttest paketimi yüklediğinde), "varsayılan" XCTest"gözlemci" sınıfını geçersiz kılın ... ( constructorişlev aracılığıyla ) PS: Anlayabildiğim kadarıyla burada yapılan her şey benim içimde eşdeğer etki ile yapılabilir sınıf + (void) load { ... }yöntemi.

  2. testlerimi çalıştırın .... bu durumda, günlüklerde daha az anlamsız ayrıntı ile (istek üzerine uygulama)

  3. "Global" XCTestObserversınıfını bozulmamış durumuna geri getirin .. XCTestBandwagon'da bulunmayan diğer koşular için faul yapmamak için (diğer adıyla bağlantılı libdemure.a). Sanırım bu tarihsel olarak yapıldı dealloc.. ama o eski cadı ile uğraşmak üzereyim.

Yani...

#define USER_DEFS NSUserDefaults.standardUserDefaults

@interface      DemureTestObserver : XCTestObserver @end
@implementation DemureTestObserver

__attribute__((constructor)) static void hijack_observer() {

/*! here I totally hijack the default logging, but you CAN
    use multiple observers, just CSV them, 
    i.e. "@"DemureTestObserverm,XCTestLog"
*/
  [USER_DEFS setObject:@"DemureTestObserver" 
                forKey:@"XCTestObserverClass"];
  [USER_DEFS synchronize];
}

__attribute__((destructor)) static void reset_observer()  {

  // Clean up, and it's as if we had never been here.
  [USER_DEFS setObject:@"XCTestLog" 
                forKey:@"XCTestObserverClass"];
  [USER_DEFS synchronize];
}

...
@end

Bağlayıcı bayrak olmadan ... (Moda-polis sürüsü Cupertino intikam talebiyle henüz Apple'ın varsayılan basıyorsa, istendiği gibi burada )

resim açıklamasını buraya girin

İLE -ldemure.abağlayıcı bayrak ... (Kapsamlı sonuçlar, gasp ... "teşekkürler constructor/ destructor" ... Crowd alkış ) resim açıklamasını buraya girin


1

İşte başka bir somut örnek: Paylaşılan bir kütüphane içindir. Paylaşılan kütüphanenin ana işlevi bir akıllı kart okuyucuyla iletişim kurmaktır. Ancak çalışma zamanında udp üzerinden 'yapılandırma bilgileri' de alabilir. UDP, başlangıç ​​zamanında başlatılması GEREKEN bir iş parçacığı tarafından işlenir .

__attribute__((constructor))  static void startUdpReceiveThread (void) {
    pthread_create( &tid_udpthread, NULL, __feigh_udp_receive_loop, NULL );
    return;

  }

Kütüphane c.


1
Sıradan genel değişken kurucuları C ++ 'da pre-main kodunu çalıştırmanın deyimsel yolu olduğundan, kütüphane C ++ ile yazılmışsa garip bir seçim.
Nicholas Wilson

@NicholasWilson Kütüphane aslında c. Nasıl c yerine c ++ yazdım bilmiyorum.
drlolly
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.