Eksik #include'un programı zamanında kırması mümkün mü?


31

#includeDerleme devam ederken eksik bir çalışma zamanında yazılımı kıracak herhangi bir durum var mı ?

Başka bir deyişle,

#include "some/code.h"
complexLogic();
cleverAlgorithms();

ve

complexLogic();
cleverAlgorithms();

her ikisi de başarılı bir şekilde inşa edilir, ancak farklı davranır mı?


1
Muhtemelen sizin eklentilerinizle, işlevlerin uygulanması tarafından kullanılanlardan farklı olan kodu yeniden tanımlanmış yapıları getirebilirsiniz. Bu ikili uyumsuzluğa yol açabilir. Bu gibi durumlar derleyici ve bağlayıcı tarafından ele alınamaz.
armagedescu

11
Bu kesinlikle. Bu başlık #included'den sonra gelen kodun anlamını tamamen değiştiren bir başlıkta makroların tanımlanması oldukça kolaydır .
Peter

4
Eminim Code Golf buna dayalı en az bir meydan okuma yaptı.
Mark

6
Belirli bir gerçek dünya örneğine işaret etmek istiyorum: Bellek sızıntısı tespiti için VLD kütüphanesi . Bir program VLD etkinken sona erdiğinde, algılanan tüm bellek sızıntılarını bazı çıkış kanallarında yazdırır. VLD kütüphanesine bağlanarak ve #include <vld.h>kodunuzda stratejik bir konuma tek bir satır yerleştirerek bir programa entegre edersiniz. Bu VLD üstbilgisini kaldırmak veya eklemek programı "bozmaz", ancak çalışma zamanı davranışını önemli ölçüde etkiler. VLD'nin bir programı kullanılamaz hale gelene kadar yavaşlattığını gördüm.
Haliburton

Yanıtlar:


40

Evet, mükemmel bir şekilde mümkün. Pek çok yol olduğundan eminim, ancak içerme dosyasının yapıcı olarak adlandırılan genel bir değişken tanımı içerdiğini varsayalım. İlk durumda kurucu yürütür ve ikincisinde yapmaz.

Üstbilgi dosyasına genel bir değişken tanımı koymak zayıf stildir, ancak mümkündür.


1
<iostream>standart kütüphanede tam olarak bunu yapar; herhangi bir çeviri birimi içeriyorsa <iostream>, std::ios_base::Initstatik nesne program başlangıcında, karakter akışları std::coutvb.
ecatmur

33

Evet, bu mümkün.

#includeS ile ilgili her şey derleme zamanında gerçekleşir. Ancak derleme zamanı elbette çalışma zamanında davranışı değiştirebilir:

some/code.h:

#define FOO
int foo(int a) { return 1; }

sonra

#include <iostream>
int foo(float a) { return 2; }

#include "some/code.h"  // Remove that line

int main() {
  std::cout << foo(1) << std::endl;
  #ifdef FOO
    std::cout << "FOO" std::endl;
  #endif
}

İle #includeaşırı yük çözünürlüğü daha uygun bulur foo(int)ve 1bunun yerine yazdırır 2. Ayrıca FOOtanımlandığından beri ek olarak yazdırır FOO.

Bu, hemen aklıma gelen iki (alakasız) örnek ve eminim daha fazlası var.


14

Önemsiz derleme yönergelerini önemsiz duruma işaret etmek için:

// main.cpp
#include <iostream>
#include "trouble.h" // comment this out to change behavior

bool doACheck(); // always returns true

int main()
{
    if (doACheck())
        std::cout << "Normal!" << std::endl;
    else
        std::cout << "BAD!" << std::endl;
}

Ve sonra

// trouble.h
#define doACheck(...) false

Belki patolojiktir, ancak bununla ilgili bir durum vardı:

#include <algorithm>
#include <windows.h> // comment this out to change behavior

using namespace std;

double doThings()
{
    return max(f(), g());
}

Zararsız görünüyor. Aramaya çalışır std::max. Ancak windows.h, maks.

#define max(a, b)  (((a) > (b)) ? (a) : (b))

Bu ise std::max, bu kez, normal işlev çağrısı bu değerlendirir f () ve bir kez gr () olur. Ancak orada windows.h ile, f () veya g () öğesini iki kez değerlendirir: bir kez karşılaştırma sırasında ve bir kez dönüş değerini almak için. F () veya g () idempotent değilse, bu sorunlara neden olabilir. Örneğin, bunlardan biri her seferinde farklı bir sayı döndüren bir sayaç olursa ....


Pencerenin maksimum işlevini çağırmak için +1, uygulama kötülüğü ve her yerde taşınabilirlik için bir bane gibi gerçek bir dünya örneği.
Scott M

3
OTOH, kurtulur using namespace std;ve kullanırsanız std::max(f(),g());, derleyici sorunu yakalar (belirsiz bir mesajla, ancak en azından çağrı sitesini işaret eder).
Ruslan

@Ruslan Oh, evet. Eğer şans verilirse, bu en iyi plan. Ama bazen biri eski kodla çalışıyor ... (hayır ... acı değil. Hiç acı değil!)
Cort Ammon

4

Bir şablon uzmanlığını kaçırmak mümkündür.

// header1.h:

template<class T>
void algorithm(std::vector<T> &ts) {
    // clever algorithm (sorting, for example)
}

class thingy {
    // stuff
};

// header2.h

template<>
void algorithm(std::vector<thingy> &ts) {
    // different clever algorithm
}

// main.cpp

#include <vector>
#include "header1.h"
//#include "header2.h"

int main() {
    std::vector<thingy> thingies;
    algorithm(thingies);
}

4

İkili uyumsuzluk, bir üyeye erişmek veya daha da kötüsü, yanlış sınıfın bir işlevini çağırmak:

#pragma once

//include1.h:
#ifndef classw
#define classw

class class_w
{
    public: int a, b;
};

#endif

Bir işlev onu kullanır ve sorun değildir:

//functions.cpp
#include <include1.h>
void smartFunction(class_w& x){x.b = 2;}

Sınıfın başka bir versiyonunu getirmek:

#pragma once

//include2.h:
#ifndef classw
#define classw

class class_w
{
public: int a;
};

#endif

Main içindeki işlevleri kullanarak, ikinci tanım sınıf tanımını değiştirir. İkili uyumsuzluğa yol açar ve çalışma zamanında çöker. Main.cpp dosyasındaki ilk eklemeyi kaldırarak sorunu düzeltin:

//main.cpp

#include <include2.h> //<-- Remove this to fix the crash
#include <include1.h>

void smartFunction(class_w& x);
int main()
{
    class_w w;
    smartFunction(w);
    return 0;
}

Varyantların hiçbiri bir derleme veya bağlantı süresi hatası oluşturmaz.

Tam tersi bir durum, içerme ekleyerek çökmeyi giderir:

//main.cpp
//#include <include1.h>  //<-- Add this include to fix the crash
#include <include2.h>
...

Bu durumlar, programın eski bir sürümünde hatayı giderirken veya harici bir kütüphane / dll / paylaşılan nesne kullanırken çok daha zordur. Bu nedenle bazen ikili geriye dönük uyumluluk kurallarına uyulmalıdır.


İkinci başlık ifndef nedeniyle eklenmez. Aksi takdirde derlenmez (sınıfın yeniden tanımlanmasına izin verilmez).
Igor R.Mar

@IgorR. Dikkatli olun. İkinci başlık (include1.h), ilk kaynak kodunda yer alan tek başlıktır. Bu ikili uyumsuzluğa yol açar. Bu, içerme işleminin çalışma zamanında çökmeye nasıl yol açabileceğini anlamak için kodun amacıdır.
armagedescu

1
@IgorR. bu, böyle bir durumu gösteren çok basit bir koddur. Ancak gerçek hayatta durum çok daha karmaşık bir ustalık olabilir. Tüm paketi yeniden yüklemeden bazı programları yamalamayı deneyin. Geriye dönük ikili uyumluluk kurallarına kesinlikle uyulması gereken tipik durumdur. Aksi takdirde yama yapmak imkansız bir iştir.
armagedescu

"İlk kaynak kodunun" ne olduğundan emin değilim, ancak 2 çeviri biriminin bir sınıfın 2 farklı tanımı olduğunu kastediyorsanız, ODR ihlali, yani tanımsız davranıştır.
Igor R.

1
Bu, C ++ Standardı tarafından tanımlandığı gibi tanımlanmamış bir davranıştır . FWIW, elbette, bu şekilde bir UB'ye neden olmak mümkündür ...
Igor

3

Sorunun C'de de mevcut olduğunu belirtmek istiyorum.

Derleyiciye bir işlevin bir çağrı kuralı kullandığını söyleyebilirsiniz. Bunu yapmazsanız, derleyicinin derleyiciyi derlemeyi reddedebileceği C ++ 'dan farklı olarak, varsayılanı kullandığını tahmin etmesi gerekir.

Örneğin,

main.c

int main(void) {
  foo(1.0f);
  return 1;
}

foo.c

#include <stdio.h>

void foo(float x) {
  printf("%g\n", x);
}

Linux'ta x86-64'te, çıktım

0

Prototipi burada atlarsanız, derleyici,

int foo(); // Has different meaning in C++

Ve belirtilmemiş argüman listeleri için bu floatkuralın geçirilmesi için dönüştürülmesi gerekir double. Vermiş olmama rağmen 1.0f, derleyici onu 1.0dgeçmek için dönüştürür foo. Ve System V Uygulama İkili Arabirim AMD64 Mimari İşlemci Eki göre, en doubleaz 64 önemli bit geçti xmm0. Ancak foobir kayan nokta bekler ve onu en az 32 önemli bitten okur xmm0ve 0 alır.

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.