#pragma가 자동으로 가정되지 않는 이유는 무엇입니까?


81

파일을 한 번만 포함하도록 컴파일러에 구체적으로 말하는 이유는 무엇입니까? 기본적으로 말이되지 않습니까? 단일 파일을 여러 번 포함해야하는 이유가 있습니까? 왜 그것을 가정하지 않습니까? 특정 하드웨어와 관련이 있습니까?


24
단일 파일을 여러 번 포함해야하는 이유가 있습니까? => 그럴 수 있습니다. 파일에 조건부 컴파일이있을 수 있습니다 #ifdefs. 그래서 당신은 말할 수 #define MODE_ONE 1#include "has-modes.h", 다음 #undef MODE_ONE#define MODE_TWO 1#include "has-modes.h"다시. 전처리 기는 이러한 종류에 대해 불가지론 적이며 때로는 이해할 수 있습니다.
HostileFork은 그나마 신뢰 SE 말한다

66
기본값으로 의미가 있습니다. C 프로그래머가 여전히 말을 타고 총을 들고 16KB의 메모리를 가지고있을 때 선택한 것은 아닙니다
Hans Passant

11
동일한 소스 파일에의 <assert.h>다른 정의를 사용하여 여러 번 포함 할 수 있습니다 NDEBUG.
Pete Becker

3
#pragma once자체로는 제대로 작동하지 않는 하드웨어 환경 (일반적으로 네트워크 드라이브 및 동일한 헤더에 대한 가능한 다중 경로 포함)이 있습니다.
Pete Becker

12
당신이 #pragma once가정 했다면 , 그 기본값에 대응하는 방법은 무엇입니까? #pragma many? 얼마나 많은 컴파일러가 이와 같은 것을 구현 했습니까?
Jonathan Leffler

답변:


84

여기에 여러 관련 질문이 있습니다.

  • #pragma once자동으로 시행되지 않는 이유는 무엇 입니까?
    파일을 두 번 이상 포함하려는 상황이 있기 때문입니다.

  • 파일을 여러 번 포함하려는 이유는 무엇입니까?
    다른 답변에는 여러 가지 이유가 있습니다 (Boost. Preprocessor, X-Macros, 데이터 파일 포함). "코드 중복 방지"의 특정 예를 추가하고 싶습니다. OpenFOAM#include함수 내에서 비트와 조각을 공통 개념으로 사용 하는 스타일을 권장합니다 . 예를 들어이 토론을 참조하십시오 .

  • 좋아,하지만 왜 옵트 아웃이 기본값이 아닌가?
    실제로 표준에 지정되어 있지 않기 때문입니다. #pragma는 정의상 구현 특정 확장입니다.

  • #pragma once널리 지원되는 기능이므로 아직 표준화 된 기능 이 되지 않은 이유는 무엇 입니까?
    플랫폼에 구애받지 않는 방식으로 "동일한 파일"을 고정하는 것은 실제로 놀랍도록 어렵 기 때문입니다. 자세한 내용은이 답변을 참조하십시오 .



4
특히, 실패했지만 가드 포함이 작동했을 경우에 대해서는이 예제참조하십시오pragma once . 위치별로 파일을 식별하는 것도 작동하지 않습니다. 왜냐하면 같은 파일이 프로젝트에서 여러 번 발생하기 때문입니다 (예 : 헤더에 헤더 전용 라이브러리를 포함하고 자체 사본을 확인하는 2 개의 하위 모듈이 있음)
MM

6
모든 pragma가 구현 별 확장은 아닙니다. 예 : #pragma STDC가족 . 그러나 그들은 모두 구현 정의 동작을 제어합니다.
Ruslan

3
@ user4581301이 답변은 pragma의 문제를 한 번 과장하고 경비원 포함으로 인한 문제를 고려하지 않습니다. 두 경우 모두 약간의 징계가 필요합니다. include 가드를 사용하면 다른 파일에서 사용되지 않는 이름을 사용해야합니다 (파일 복사 수정 후에 발생 함). 한 번 pragma를 사용하면 파일의 고유 한 위치를 결정해야합니다. 결국 좋은 일입니다.
Oliv

3
@Mehrdad : 컴파일러가 소스 파일에 쓰는 것을 진지하게 제안하고 있습니까!? 컴파일러가을 보면 전체 파일을 읽을 때까지 #ifndef XX해당 뒤에 무엇이 있는지 알 수 없습니다 #endif. 가장 바깥 쪽 여부를 추적 컴파일러 #ifndef전체 파일 및 매크로 그것을 검사 파일을 다시 검색하지 않도록 할 수 있습니다 어떤 메모를 둘러싸는하지만, 할 수있는 지침 말할 것 같다 현재 위치에 따라 중요성의 아무것도가에 컴파일러에 의존하기보다 더 좋은 것 그런 것들을 기억하십시오.
supercat nov.

38

함수 내부와 같이 전역 범위뿐만 아니라 파일의 #include 모든 위치 에서 사용할 수 있습니다 (필요한 경우 여러 번). 물론, 추악하고 좋지 않은 스타일이지만 가능하고 때로는 합리적입니다 (포함하는 파일에 따라 다름). 경우는 #include오직 다음 한 번 일이없는 것 일이었다. #include결국 멍청한 텍스트 대체 (잘라 내기 및 붙여 넣기)를 수행하고 포함하는 모든 것이 헤더 파일 일 필요는 없습니다. 예를 들어 #include, 원시 데이터를 포함하는 자동 생성 데이터가 포함 된 파일을 std::vector. 처럼

std::vector<int> data = {
#include "my_generated_data.txt"
}

그리고 "my_generated_data.txt"는 컴파일 중에 빌드 시스템에 의해 생성 된 것입니다.

또는 내가 게으르고 / 어리 석고 / 멍청해서 이것을 파일에 넣을 수도 있습니다 ( 매우 인위적인 예) :

const noexcept;

그리고 나는

class foo {
    void f1()
    #include "stupid.file"
    int f2(int)
    #include "stupid.file"
};

약간 덜 고안된 또 다른 예는 많은 함수가 네임 스페이스에서 많은 양의 유형을 사용해야하는 소스 파일이 될 것입니다.하지만 using namespace foo;전역 네임 스페이스를 다른 많은 것들로 오염시킬 수 있기 때문에 전역 적으로 말하고 싶지는 않습니다. 당신은 원하지 않습니다. 따라서 다음을 포함하는 "foo"파일을 만듭니다.

using std::vector;
using std::array;
using std::rotate;
... You get the idea ...

그런 다음 소스 파일에서이 작업을 수행합니다.

void f1() {
#include "foo" // needs "stuff"
}

void f2() {
    // Doesn't need "stuff"
}

void f3() {
#include "foo" // also needs "stuff"
}

참고 : 나는 이와 같은 일을 옹호하지 않습니다. 그러나 가능하고 일부 코드베이스에서 수행되며 왜 허용되지 않아야하는지 모르겠습니다. 그것은 않습니다 그 용도가있다.

또한 포함하는 파일이 특정 매크로 ( #defines) 의 값에 따라 다르게 동작 할 수도 있습니다 . 따라서 먼저 일부 값을 변경 한 후 여러 위치에 파일을 포함 할 수 있으므로 소스 파일의 다른 부분에서 다른 동작을 얻을 수 있습니다.


1
모든 헤더가 한 번 pragma이면 여전히 작동합니다. 생성 된 데이터를 두 번 이상 포함하지 않는 한.
PSkocik

2
@PSkocik하지만 한 번 이상 포함 해야 할 수도 있습니다. 내가 할 수없는 이유는 무엇입니까?
Jesper Juhl

2
@JesperJuhl 그게 요점입니다. 한 번 이상 포함 할 필요 가 없습니다 . 현재 옵션이 있지만 대안이 훨씬 나쁘지는 않습니다.
Johnny Cache

9
@PSkocik #define포함하기 전에 s 값 을 변경하여 포함 된 파일의 동작을 변경하면 소스 파일의 다른 부분에서 다른 동작을 가져 오기 위해 여러 번 포함해야 할 수 있습니다.
Jesper Juhl

27

여러 번 포함하는 것은 예를 들어 X- 매크로 기술을 사용하여 사용할 수 있습니다 .

data.inc :

X(ONE)
X(TWO)
X(THREE)

use_data_inc_twice.c

enum data_e { 
#define X(V) V,
   #include "data.inc"
#undef X
};
char const* data_e__strings[]={
#define X(V) [V]=#V,
   #include "data.inc"
#undef X
};

다른 용도에 대해서는 잘 모릅니다.


그것은 지나치게 복잡하게 들립니다. 처음에 이러한 정의를 파일에 포함하지 않는 이유는 무엇입니까?
Johnny Cache

2
@JohnnyCache :이 예제는 X- 매크로의 작동 방식을 단순화 한 버전입니다. 링크를 읽으십시오. 테이블 형식 데이터의 복잡한 조작을 수행하는 경우에 매우 유용합니다. X- 매크로를 많이 사용하는 경우 "파일에 해당 정의를 포함"할 수있는 방법은 없습니다.
Nicol Bolas 2018

2
@Johnny-예-한 가지 좋은 이유는 일관성을 보장하는 것입니다 (요소가 수십 개에 불과할 때는 손으로하기 어렵고 수백 개는 신경 쓰지 마십시오).
Toby Speight 2018

@TobySpeight Heh, 다른 곳에 수천 개의 코드를 작성하지 않도록 한 줄의 코드를 절약 할 수 있다고 생각합니다. 말이된다.
Johnny Cache

1
중복을 피하기 위해. 특히 파일이 큰 경우. 물론 X 매크로 목록을 포함하는 큰 매크로를 사용할 수 있지만 프로젝트에서이를 사용할 수 있기 때문에 명령 #pragma once동작은 주요 변경 사항이 될 것입니다.
PSkocik

21

언어에 존재하는 "#include"기능의 목적은 프로그램을 여러 컴파일 단위로 분해하는 지원을 제공하는 것이라는 가정하에 작동하는 것 같습니다. 그것은 틀 렸습니다.

그 역할을 수행 할 수는 있지만 의도 된 목적은 아닙니다. C는 원래 Unix를 다시 구현하기 위해 PDP-11 Macro-11 Assembly 보다 약간 높은 수준의 언어로 개발 되었습니다 . 그것은 Macro-11의 기능이기 때문에 매크로 전처리기를 가지고있었습니다. 다른 파일의 매크로를 텍스트로 포함 할 수있는 기능이 있었는데, 이는 그들이 새로운 C 컴파일러로 포팅하는 기존 유닉스가 사용했던 Macro-11의 기능 이었기 때문입니다.

이제 "#include"는 코드를 컴파일 단위로 분리하는 데 유용 하다는 것이 밝혀졌습니다 . 그러나이 해킹이 존재한다는 사실은 C로 수행되는 방법이되었음을 의미합니다. 방법이 존재한다는 사실 은이 기능을 제공하기 위해 새로운 방법 을 만들 필요 가 없음을 의미 하므로 더 안전한 것은 없습니다 (예 : 다중 공격에 취약하지 않음). -포함)이 생성되었습니다. 이미 C에 있었기 때문에 나머지 C 구문 및 관용구 대부분과 함께 C ++로 복사되었습니다.

C ++ 에 적절한 모듈 시스템 을 제공하기위한 제안 이있어이 45 년 된 전 처리기 해킹을 마침내 생략 할 수 있습니다. 이것이 얼마나 임박한지 모르겠습니다. 나는 그것이 10 년 넘게 작동하고 있다는 이야기를 들었습니다.


5
평소처럼 C와 C ++를 이해하려면 그 역사를 이해해야합니다.
Jack Aidley

모듈이 2 월에 출시 될 것으로 예상 하는 것이 합리적 입니다.
Davis Herring

7
@DavisHerring-예,하지만 어느 2 월?
TED

10

아니오, 이것은 예를 들어 도서관 작성자가 사용할 수있는 옵션을 크게 방해합니다. 예를 들어 Boost.Preprocessor를 사용하면 전 처리기 루프를 사용할 수 있으며이를 달성하는 유일한 방법은 동일한 파일을 여러 개 포함하는 것입니다.

그리고 Boost.Preprocessor는 매우 유용한 많은 라이브러리를위한 빌딩 블록입니다.


1
그것은 그 어떤 것도 방해 하지 않을 것 입니다. OP 는 변경 불가능한 동작이 아닌 기본 동작 에 대해 물었습니다 . 기본값을 변경하고 대신 #pragma reentrant이러한 라인을 따라 전 처리기 플래그 또는 무언가 를 제공하는 것이 전적으로 합리적 입니다. Hindsight는 20/20입니다.
Konrad Rudolph

사람들이 라이브러리와 종속성 @KonradRudolph를 업데이트하도록 강요한다는 의미에서 방해가 될 것입니다. 항상 문제는 아니지만 일부 레거시 프로그램에서 문제를 일으킬 수 있습니다. 이상적 으로는이 문제 나 기타 잠재적 문제를 완화하기 위해 기본값이 once또는 인지 여부를 지정하는 명령 줄 스위치도 있습니다 reentrant.
Justin Time-Monica 복원

1
@JustinTime 글쎄요, 내 의견은 분명히 이전 버전과 호환되는 (따라서 실행 가능한) 변경이 아니라고 말합니다. 그러나 문제는 왜 처음 에 그렇게 설계되었는지가 아니라 변경되지 않는 이유였습니다. 그에 대한 대답은 분명 원래의 디자인이 엄청난 결과를 가져 오는 큰 실수 였다는 것입니다.
Konrad Rudolph

8

제가 주로 작업하는 제품의 펌웨어에서 함수와 전역 / 정적 변수가 메모리에 할당되어야하는 위치를 지정할 수 있어야합니다. 실시간 처리는 프로세서가 직접 빠르게 액세스 할 수 있도록 칩의 L1 메모리에 있어야합니다. 덜 중요한 처리는 L2 메모리 온 칩에서 진행될 수 있습니다. 특히 신속하게 처리 할 필요가없는 것은 외부 DDR에 상주하여 캐싱을 통과 할 수 있습니다. 조금 느려도 문제가되지 않기 때문입니다.

일이 진행되는 위치를 할당하는 #pragma는 길고 사소한 줄입니다. 오해하기 쉬울 것입니다. 잘못을 얻기의 효과는 코드 / 데이터가 자동으로 기본에 (DDR) 메모리를 넣어 것, 그리고 효과가 있다는 것 그이 쉽게 알 수없는 이유로 작동 중지 제어 루프 폐쇄 될 수 있습니다.

그래서 저는 그 pragma 만 포함하는 include 파일을 사용합니다. 내 코드는 이제 다음과 같습니다.

헤더 파일 ...

#ifndef HEADERFILE_H
#define HEADERFILE_H

#include "set_fast_storage.h"

/* Declare variables */

#include "set_slow_storage.h"

/* Declare functions for initialisation on startup */

#include "set_fast_storage.h"

/* Declare functions for real-time processing */

#include "set_storage_default.h"

#endif

그리고 소스 ...

#include "headerfile.h"

#include "set_fast_storage.h"

/* Define variables */

#include "set_slow_storage.h"

/* Define functions for initialisation on startup */

#include "set_fast_storage.h"

/* Define functions for real-time processing */

헤더에만 동일한 파일이 여러 개 포함되어 있음을 알 수 있습니다. 지금 뭔가를 잘못 입력하면 컴파일러가 "set_fat_storage.h"포함 파일을 찾을 수 없다는 메시지를 표시하고 쉽게 수정할 수 있습니다.

따라서 귀하의 질문에 대한 대답으로 이것은 다중 포함이 필요한 실제적이고 실용적인 예입니다.


3
나는 당신의 사용 사례가 _Pragma지침에 대한 동기를 부여하는 예라고 말하고 싶습니다 . 이제 동일한 pragma를 일반 매크로에서 확장 할 수 있습니다. 따라서 두 번 이상 포함 할 필요가 없습니다.
StoryTeller-Unslander Monica
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.