상수에 #define 또는 const int를 사용하는 것이 더 낫습니까?


26

Arduino는 홀드 하이브리드이며 일부 C ++ 기능은 임베디드 환경에서 전통적으로 C 환경으로 사용됩니다. 실제로 많은 Arduino 코드는 C와 매우 유사합니다.

C는 전통적 #define으로 상수에 s를 사용했습니다 . 이에 대한 여러 가지 이유가 있습니다.

  1. 을 사용하여 배열 크기를 설정할 수 없습니다 const int.
  2. const intcase 문 레이블로 사용할 수 없습니다 (일부 컴파일러에서는 작동하지만)
  3. const다른로를 초기화 할 수 없습니다 const.

더 많은 추론을 위해 StackOverflow 에서이 질문 을 확인할 수 있습니다 .

그렇다면 Arduino에 무엇을 사용해야합니까? 나는 경향이 #define있지만 일부 코드 const는 블렌드를 사용하고 있습니다.


좋은 옵티마이 저는 그것을 약하게 만들 것입니다
래칫 괴물

3
정말? 컴파일러가 형식 안전성과 같은 것을 해결하고 배열 길이 등을 정의하는 데 사용할 수없는 방법을 알지 못합니다.
Cybergibbons

동의한다. 또한 아래의 답변을 보면 실제로 사용할 유형을 모르는 상황이 있음을 알 수 있으므로 #define분명한 선택입니다. 내 예는 A5와 같은 아날로그 핀의 이름을 지정하는 것입니다. 사용할 수있는 적절한 유형이 없으므로 const유일한 선택은 a를 사용 #define하고 컴파일러가 의미를 해석하기 전에 텍스트 입력으로 대체하도록하는 것입니다.
SDsolar

답변:


21

C와 C ++에서 동일하게 동작 const int하지 않는다는 점에 주목하는 것이 중요합니다 . 사실 원래 질문과 Peter Bloomfields의 광범위한 답변에서 언급 된 몇 가지 반대 의견은 유효하지 않습니다.

  • C ++에서 const int상수는 컴파일 시간 값이며 대소 문자 레이블 등으로 배열 제한을 설정하는 데 사용할 있습니다.
  • const int상수가 반드시 스토리지를 차지하지는 않습니다. 자신의 주소를 취하거나 extern으로 선언하지 않는 한 일반적으로 컴파일 시간이 있습니다.

그러나 정수 상수의 경우 종종 (이름 또는 익명)을 사용하는 것이 좋습니다 enum. 나는 종종 이것을 좋아하기 때문에 :

  • C와 역 호환됩니다.
  • const intC ++ 11에서는 거의 안전 합니다.
  • 관련 상수를 자연스럽게 그룹화하는 방법을 제공합니다.
  • 네임 스페이스 제어를 위해이를 사용할 수도 있습니다.

따라서 관용적 C ++ 프로그램에서는 #define정수 상수를 정의하는 데 사용할 이유가 없습니다 . C 요구 사항을 유지하고 싶더라도 (기술 요구 사항으로 인해, 구식 학교를 다니거나 함께 일하는 사람들이 그런 방식으로 선호하기 때문에) 계속 사용할 enum수 있고 대신 사용해야 #define합니다.


2
당신은 몇 가지 훌륭한 점을 제기합니다 (특히 배열 제한에 관해서는-아직 Arduino IDE를 사용하는 표준 컴파일러는 아직 지원하지 않았습니다). 컴파일 타임 상수는 사용되는 곳 어디에서나 코드 (예 : SRAM 대신 프로그램 메모리)에서 값이 발생해야하기 때문에 저장 시간을 사용하지 않는다고 말하는 것은 정확하지 않습니다. 즉, 포인터보다 더 많은 공간을 차지하는 모든 유형에 대해 사용 가능한 Flash에 영향을줍니다.
피터 블룸필드

1
"실제로 원래의 질문에서 암시 된 몇 가지 반대 의견"-이것이 원래의 질문에서 유효하지 않은 이유는 무엇입니까?
Cybergibbons

@Cybergibbons Arduino는 C ++을 기반으로하므로 어떤 이유로 코드가 C와 호환되어야하는 경우를 제외하고 C 전용 제약 조건이 적절한 이유는 분명하지 않습니다.
microtherion

3
@ PeterR.Bloomfield, 추가 스토리지가 필요하지 않은 상수에 대한 나의 요점은에 국한되었습니다 const int. 더 복잡한 유형의 경우 스토리지가 할당 될 수 있지만, #define. 보다 더 나쁘지는 않습니다 .
microtherion

7

편집 : microtherion은 특히 메모리 사용에 대한 내 요점 중 일부를 수정하는 훌륭한 대답을 제공합니다.


알다시피 #define컴파일러가 const변수를 허용하지 않기 때문에 를 사용해야하는 특정 상황이 있습니다 . 마찬가지로 일부 상황에서는 값 배열이 필요할 때 (예 :의 배열을 가질 수없는 경우) 변수를 사용해야합니다 #define.

그러나 반드시 하나의 '올바른'답변이 반드시 필요한 것은 아닙니다. 다음은 내가 따르는 몇 가지 지침입니다.

유형 안전
일반적인 프로그래밍 관점에서 const변수가 일반적으로 바람직합니다 (가능한 경우). 그 주된 이유는 형식 안전입니다.

#define(처리기 매크로) 직접 복사 문자 코드의 위치에 각 값마다 독립적으로 사용하기. 결과적으로 사용 방법 / 위치에 따라 유형이 다르게 해석 될 수 있기 때문에 모호성이 생길 수 있습니다.

const변수는 선언에 의해 결정하고, 초기화하는 동안 해결 오직 한 종류입니다. 암시 적으로 유형을 승격시킬 수있는 다양한 상황이 있지만, 다르게 동작하기 전에 명시 적 캐스트가 필요한 경우가 종종 있습니다. 최소한 형식 문제가 발생할 때 컴파일러는 (올바르게 구성된 경우) 더 안정적인 경고를 표시 할 수 있습니다.

가능한 해결 방법은에 명시 적 캐스트 또는 유형 접미사를 포함시키는 것 #define입니다. 예를 들면 다음과 같습니다.

#define THE_ANSWER (int8_t)42
#define NOT_QUITE_PI 3.14f

이 방법은 사용 방법에 따라 일부 경우 구문 문제를 일으킬 수 있습니다.

메모리 사용
범용 컴퓨팅과 달리 메모리는 Arduino와 같은 것을 처리 할 때 분명히 중요합니다. const변수 대 a를 사용 #define하면 데이터가 메모리에 저장되는 위치에 영향을 미치므로 둘 중 하나를 사용해야 할 수 있습니다.

  • const 변수는 (보통) 다른 모든 변수와 함께 SRAM에 저장됩니다.
  • 사용 된 리터럴 값 #define은 종종 스케치 자체와 함께 프로그램 공간 (플래시 메모리)에 저장됩니다.

(컴파일러 구성 및 최적화와 같이 무언가를 저장하는 방법과 위치에 정확하게 영향을 줄 수있는 다양한 요소가 있습니다.)

SRAM과 Flash는 서로 다른 제한이 있습니다 (예 : Uno의 경우 각각 2KB와 32KB). 일부 응용 프로그램의 경우 SRAM이 부족하기 때문에 일부 항목을 Flash로 전환하는 것이 도움이 될 수 있습니다. 덜 일반적이지만 그 반대도 가능합니다.

PROGMEM
프로그램 공간 (플래시)에 데이터를 저장하는 동시에 형식 안전성의 이점을 얻을 수 있습니다. PROGMEM키워드를 사용하여 수행됩니다 . 모든 유형에서 작동하지는 않지만 일반적으로 정수 또는 문자열 배열에 사용됩니다.

설명서에 제시된 일반적인 형식 은 다음과 같습니다.

dataType variableName[] PROGMEM = {dataInt0, dataInt1, dataInt3...}; 

문자열 테이블은 좀 더 복잡하지만 설명서에 자세한 내용이 있습니다.


1

실행 중에 변경되지 않은 지정된 유형의 변수의 경우 일반적으로 사용할 수 있습니다.

변수에 포함 된 디지털 핀 번호의 경우 다음과 같이 작동 할 수 있습니다.

const int ledPin = 13;

하지만 내가 항상 사용하는 상황이 하나 있습니다 #define

영숫자이므로 아날로그 핀 번호를 정의해야합니다.

물론, 당신은 하드 코드 핀 번호 수 a2, a3등 모든 프로그램 전반에 걸쳐 컴파일러가 그들과 함께 무엇을 알 수 있습니다. 그런 다음 핀을 변경하면 각 용도를 변경해야합니다.

또한, 나는 항상 핀 정의를 한곳에서 맨 위에두기를 원하므로 const로 정의 된 핀에 적합한 유형이 질문이 됩니다 A5.

그런 경우 항상 사용합니다 #define

전압 분배기 예 :

//
//  read12     Reads Voltage of 12V Battery
//
//        SDsolar      8/8/18
//
#define adcInput A5    // Voltage divider output comes in on Analog A5
float R1 = 120000.0;   // R1 for voltage divider input from external 0-15V
float R2 =  20000.0;   // R2 for voltage divider output to ADC
float vRef = 4.8;      // 9V on Vcc goes through the regulator
float vTmp, vIn;
int value;
.
.
void setup() {
.
// allow ADC to stabilize
value=analogRead(adcPin); delay(50); value=analogRead(adcPin); delay(50);
value=analogRead(adcPin); delay(50); value=analogRead(adcPin); delay(50);
value=analogRead(adcPin); delay(50); value=analogRead(adcPin);
.
void loop () {
.
.
  value=analogRead(adcPin);
  vTmp = value * ( vRef / 1024.0 );  
  vIn = vTmp / (R2/(R1+R2)); 
 .
 .

모든 설정 변수가 맨 위에 있으며 adcPin컴파일 타임 을 제외하고 는 값이 변경되지 않습니다 .

어떤 유형인지 걱정할 필요가 없습니다 adcPin. 그리고 상수를 저장하기 위해 바이너리에 여분의 RAM이 사용되지 않습니다.

컴파일러는 컴파일하기 전에 각 인스턴스를 adcPin문자열로 바꿉니다 A5.


다른 결정 방법을 논의하는 흥미로운 Arduino 포럼 스레드가 있습니다.

#define vs. const 변수 (아두 이노 포럼)

발췌 :

코드 대체 :

#define FOREVER for( ; ; )

FOREVER
 {
 if (serial.available() > 0)
   ...
 }

디버깅 코드 :

#ifdef DEBUG
 #define DEBUG_PRINT(x) Serial.println(x)
#else
 #define DEBUG_PRINT(x)
#endif

RAM을 저장하기위한 정의 truefalse부울

Instead of using `const bool true = 1;` and same for `false`

#define true (boolean)1
#define false (boolean)0

많은 것들이 개인 취향에 달려 있지만 #define더 다재다능 하다는 것은 분명합니다 .


같은 상황에서는 a const보다 많은 RAM을 사용하지 않습니다 #define. 그리고 아날로그 핀의 경우 아무런 차이는 const uint8_t없지만 로 정의합니다 const int.
Edgar Bonet

당신은 "쓴 a는 const실제로 더 많은 RAM [...] 실제로 사용될 때까지 사용하지 않습니다 ." 당신은 내 요점을 놓쳤다. 대부분의 경우, a const는 RAM을 사용 하더라도 사용하지 않는다 . 그런 다음“ 이것은 멀티 패스 컴파일러입니다 ”. 가장 중요한 것은 최적화 컴파일러입니다. 가능할 때마다 상수는 즉시 피연산자 로 최적화됩니다 .
Edgar Bonet
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.