누락 된 #include가 런타임에 프로그램을 중단 할 수 있습니까?


31

누락 된 경우 #include런타임에 소프트웨어를 중단하고 빌드가 계속 진행되는 경우가 있습니까?

다시 말해,

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

complexLogic();
cleverAlgorithms();

둘 다 성공적으로 빌드되지만 다르게 동작합니까?


1
아마도 포함을 사용하면 함수 구현에 사용되는 것과 다른 구조의 코드를 재정의 한 구조로 가져올 수 있습니다. 이진 비 호환성을 초래할 수 있습니다. 이러한 상황은 컴파일러와 링커에서 처리 할 수 ​​없습니다.
armagedescu

11
확실합니다. 헤더 다음에 오는 코드의 의미를 완전히 변경하는 매크로를 헤더에 정의하는 것은 매우 쉽습니다 #include.
피터

4
나는 Code Golf 가 이것에 기초하여 적어도 하나의 도전을했다고 확신 합니다.
마크

6
메모리 누수 감지를위한 VLD 라이브러리 와 같은 실제 사례를 지적하고자합니다 . 프로그램이 VLD 활성 상태로 종료되면 감지 된 모든 메모리 누수를 일부 출력 채널에서 인쇄합니다. VLD 라이브러리에 연결하고 #include <vld.h>코드에서 전략적 위치에 한 줄을 배치하여 프로그램에 통합합니다 . 해당 VLD 헤더를 제거하거나 추가해도 프로그램이 "중단"되지는 않지만 런타임 동작에 큰 영향을 미칩니다. VLD가 프로그램을 사용할 수 없을 정도로 느리게하는 것을 보았습니다.
Haliburton

답변:


40

예, 완벽하게 가능합니다. 나는 많은 방법이 있다고 확신하지만 포함 파일에 생성자라는 전역 변수 정의가 포함되어 있다고 가정하십시오. 첫 번째 경우에는 생성자가 실행되고 두 번째 경우에는 생성되지 않습니다.

헤더 파일에 전역 변수 정의를 넣는 것은 좋지 않은 스타일이지만 가능합니다.


1
<iostream>표준 라이브러리에서 정확하게 이것을 수행합니다. 변환 단위가 포함 된 <iostream>경우 std::ios_base::Init정적 개체는 프로그램 시작시 구성되어 문자 스트림 std::cout등을 초기화합니다 . 그렇지 않으면 그렇지 않습니다.
ecatmur

33

예, 가능합니다.

에 관한 모든 것은 #include컴파일 타임에 발생합니다. 그러나 컴파일 타임은 물론 런타임에 동작을 변경할 수 있습니다.

some/code.h:

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

그때

#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
}

을 사용하면 #include과부하 해상도가 더 적절 foo(int)하고 따라서 1대신 인쇄 됩니다 2. 또한 FOO정의되었으므로 추가로 인쇄합니다 FOO.

그것은 바로 내 마음에 온 두 가지 (관련되지 않은) 예이며 더 많은 것이 있다고 확신합니다.


14

사소한 경우, 사전 컴파일러 지시문을 지적하면됩니다.

// 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;
}

그리고

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

병리학 적 일 수도 있지만 관련 사례가 발생했습니다.

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

using namespace std;

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

무해 해 보인다. 전화를 시도합니다 std::max. 그러나 windows.h는 max를

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

이것이이면 std::maxf ()를 한 번, g ()를 한 번 평가하는 일반 함수 호출입니다. 그러나 windows.h가 있으면 비교 중에 한 번, 반환 값을 얻기 위해 한 번 f () 또는 g ()를 두 번 평가합니다. f () 또는 g ()가 dem 등성이 아닌 경우 문제가 발생할 수 있습니다. 예를 들어, 그중 하나가 매번 다른 숫자를 반환하는 카운터 인 경우 ....


+1의 구현, 악의적 인 구현과 모든 곳의 이식성에 대한 골칫거리 인 Window의 최대 기능을 호출하는 +1.
Scott M

3
OTOH,를 제거 using namespace std;하고 사용 std::max(f(),g());하면 컴파일러가 문제를 잡을 수 있습니다 (흐리게 메시지가 표시되지만 적어도 호출 사이트를 가리킴).
Ruslan

@Ruslan 아, 그렇습니다. 기회가 있다면, 이것이 최선의 계획입니다. 그러나 때로는 하나는 레거시 코드로 작업하고 있습니다 ... (아니요 ... 쓴맛은 전혀 쓰지 않습니다!)
Cort Ammon

4

템플릿 전문화가 누락되었을 수 있습니다.

// 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

이진 비 호환성, 멤버에 액세스하거나 더 나쁜 클래스를 잘못된 클래스의 함수 호출

#pragma once

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

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

#endif

함수가 그것을 사용하고 괜찮습니다.

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

다른 버전의 수업 가져 오기 :

#pragma once

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

class class_w
{
public: int a;
};

#endif

main에서 함수를 사용하여 두 번째 정의는 클래스 정의를 변경합니다. 이진 비 호환성을 초래하고 런타임에 단순히 충돌합니다. main.cpp의 첫 번째 포함을 제거하여 문제를 해결하십시오.

//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;
}

어떤 변형도 컴파일 또는 링크 시간 오류를 생성하지 않습니다.

그 반대의 경우, include를 추가하면 충돌이 수정됩니다.

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

이러한 상황은 이전 버전의 프로그램에서 버그를 수정하거나 외부 라이브러리 / dll / 공유 객체를 사용할 때 훨씬 더 어렵습니다. 그렇기 때문에 때때로 바이너리 역 호환성 규칙을 따라야합니다.


두 번째 헤더는 ifndef로 인해 포함되지 않습니다. 그렇지 않으면 컴파일되지 않습니다 (클래스 재정의는 허용되지 않음).
Igor R.

@IgorR. 세심. 두 번째 헤더 (include1.h)는 첫 번째 소스 코드에만 포함됩니다. 이진 비 호환성으로 이어집니다. 이것이 코드의 목적이며, 포함시 런타임에 충돌이 발생할 수있는 방법을 보여줍니다.
armagedescu

1
@IgorR. 이것은 매우 간단한 코드이며 이러한 상황을 보여줍니다. 그러나 실제 상황에서는 훨씬 더 복잡한 미묘한 차이가 있습니다. 전체 패키지를 다시 설치하지 않고 일부 프로그램을 패치하십시오. 이진 호환성 규칙을 엄격하게 따라야하는 일반적인 상황입니다. 그렇지 않으면 패치 작업이 불가능합니다.
armagedescu

"첫 번째 소스 코드"가 무엇인지 잘 모르겠지만 2 개의 번역 단위가 클래스에 대해 2 개의 서로 다른 정의를 가짐을 의미하는 경우 ODR 위반, 즉 정의되지 않은 동작입니다.
Igor R.

1
이는 C ++ 표준에 설명 된대로 정의되지 않은 동작 입니다. 물론, 이런 식으로 UB를 발생시킬 수 있습니다.
Igor R.

3

C에도 문제가 있음을 지적하고 싶습니다.

함수에 호출 규칙을 사용하는 함수를 컴파일러에 알릴 수 있습니다. 그렇지 않으면 컴파일러는 컴파일러가 컴파일을 거부 할 수있는 C ++과 달리 기본 컴파일러를 사용한다고 추측해야합니다.

예를 들어

main.c

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

foo.c

#include <stdio.h>

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

x86-64의 Linux에서 내 출력은

0

여기서 프로토 타입을 생략하면 컴파일러는 다음과 같이 가정합니다.

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

지정되지 않은 인수 목록에 대한 규칙은 전달 float되도록 변환되어야합니다 double. 그래서 내가 주었지만 1.0f컴파일러는 그것을 1.0d전달 하도록 변환합니다 foo. 그리고 System V Application Binary Interface AMD64 Architecture Processor Supplement double에 따르면 64 비트의 최하위 비트로 전달됩니다 xmm0. 그러나 foofloat를 기대하고 32의 최하위 비트에서이를 읽고 xmm00을 얻습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.