Arduino에서 전역 변수가 악의입니까?


24

나는 프로그래밍에 비교적 익숙하지 않으며 내가 읽고있는 많은 코딩 모범 사례는 효과적으로 전역 변수를 사용해야 할 이유가 거의 없거나 최상의 코드에 전역이 없다는 것을 효과적으로 지적합니다.

SD 카드로 Arduino 인터페이스를 만들고 컴퓨터와 대화하고 모터 컨트롤러를 실행하기 위해 소프트웨어를 작성할 때 이것을 명심하기 위해 최선을 다했습니다.

저는 현재 약 1100 줄의 "초보자 수준"코드에 대한 46 개의 전역을 가지고 있습니다 (코드는 하나 이상의 작업을 수행하지 않습니다). 이것이 좋은 비율입니까, 아니면 더 많이 줄여야합니까? 또한 세계의 수를 더 줄이기 위해 어떤 관행을 사용할 수 있습니까?

나는 일반적으로 컴퓨터 프로그래밍보다는 Arduino 제품의 코딩에 대한 모범 사례에 특히 관심이 있기 때문에 여기에 묻습니다.


2
Arduino에서는 전역 변수를 피할 수 없습니다. 함수 / 메소드 범위를 벗어난 모든 변수 선언은 전역 적입니다. 따라서 함수간에 값을 공유해야하는 경우 모든 값을 인수로 전달하지 않는 한 함수는 전역이어야합니다.

16
@LookAlterno Err, 이상한 매크로와 라이브러리가있는 C ++이기 때문에 Ardunio에서 클래스를 작성할 수 없습니까? 그렇다면 모든 변수가 전역 변수 인 것은 아닙니다. 그리고 C에서도 전역 변수를 사용하는 대신 변수 (구조 내부)를 함수에 전달하는 것을 선호하는 것이 가장 좋습니다. 소규모 프로그램의 경우 편리하지 않을 수 있지만 일반적으로 프로그램의 규모가 커지고 복잡 해짐에 따라 보상을받습니다.
Muzer

11
@LookAlterno : "나는 피한다""당신은 할 수 없다" 는 매우 다른 것들입니다.
Monica와의 가벼움 경주

2
실제로 일부 임베디드 프로그래머는 로컬 변수를 금지하고 대신 전역 변수 (또는 함수 범위 정적 변수)가 필요합니다. 프로그램이 작 으면 간단한 상태 머신으로 쉽게 분석 할 수 있습니다. 변수는 서로를 덮어 쓰지 않기 때문에 (즉, 변수를 스택 및 힙 할당 할 때).
Rob

1
정적 변수와 전역 변수는 컴파일 타임에 메모리 소비를 아는 이점을 제공합니다. 사용 가능한 메모리가 매우 제한적인 arduino를 사용하면 이점이 있습니다. 초보자가 사용 가능한 메모리를 소진하고 추적 할 수없는 오류를 경험하는 것은 매우 쉽습니다.
antipattern

답변:


33

그들은 그 자체로 이 아니지만 , 그것들을 사용해야 할 이유가없는 곳에서 과도하게 사용되는 경향이 있습니다.

전역 변수가 유리한 경우가 많습니다. 특히 Arduino 프로그래밍은 PC 프로그래밍과 크게 다릅니다.

전역 변수의 가장 큰 장점은 정적 할당입니다. 특히 클래스 인스턴스와 같이 크고 복잡한 변수가있는 경우. new자원의 부족으로 인해 동적 할당 (사용 등)이 어려워집니다.

당신이 (하나의 정상적인 C 프로그램처럼 또한 단일 호출 트리를하지 않는 main()대신에 효과적으로 두 개의 나무 (얻을 - 함수가 다른 함수를 호출) setup()다음 함수를 호출 loop()때때로 전역 변수는 의미를 함수를 호출) (당신이 모두를 사용하려는 경우, 즉 유일한 방법은 당신의 목표를 달성 setup()하고 loop()).

아니, 그들은 악하지 않으며, Arduino에서는 PC보다 더 많이 사용합니다.


좋아, 내가 loop()(또는에서 호출 된 여러 함수에서 loop()) 사용하고있는 것이면 어떻게 됩니까? 처음에 정의하는 것과 다른 방식으로 설정하는 것이 더 낫습니까?
ATE-ENGE

1
이 경우 루프 ()에서 정의하고 ( static반복적으로 값을 유지하는 데 필요한 것처럼 ) 호출 체인 아래로 함수 매개 변수를 전달합니다.
Majenko

2
즉, 하나의 방법 또는 참조로 건네 void foo(int &var) { var = 4; }foo(n);- n이제 4이다
Majenko

1
클래스는 스택 할당 될 수 있습니다. 물론, 큰 인스턴스를 스택에 배치하는 것은 좋지 않지만 여전히 그렇습니다.
JAB

1
@JAB 무엇이든 스택 할당 할 수 있습니다.
Majenko

18

실제 코드를 보지 않고 결정적인 대답을하는 것은 매우 어렵습니다.

전역 변수는 악의가 없으며 일반적으로 하드웨어 액세스가 많은 임베디드 환경에서 의미가 있습니다. UART는 4 개, I2C 포트는 1 개뿐입니다. 따라서 특정 하드웨어 리소스에 연결된 변수에 전역을 사용하는 것이 좋습니다. 그리고 사실, 아두 이노 핵심 라이브러리가 작업을 수행합니다 Serial, Serial1등 글로벌 변수입니다. 또한 프로그램의 전역 상태를 나타내는 변수는 일반적으로 전역입니다.

현재 약 1100 줄의 [code]에 46 개의 전역이 있습니다. 이것은 좋은 비율입니까 [...]

숫자에 관한 것이 아닙니다. 스스로에게 물어봐야 할 올바른 질문은 이러한 각 글로벌에 대해 글로벌 범위에 해당하는 것이 적절한 지 여부입니다.

여전히 46 글로벌은 나에게 조금 높은 것 같습니다. 이들 중 일부가 일정한 값을 보유하는 경우 다음과 같이 규정 const하십시오. 컴파일러는 일반적으로 스토리지를 최적화합니다. 이러한 변수 중 하나가 단일 함수 내에서만 사용되는 경우 로컬로 만듭니다. 함수 호출 사이에 값을 유지하려면 값을로 한정하십시오 static. 클래스 내에서 변수를 그룹화하고이 클래스의 글로벌 인스턴스를 하나만 만들어서 "표시 가능한"글로벌 수를 줄일 수도 있습니다. 그러나 물건을 모으는 것이 합리적 일 때만 그렇게하십시오. GlobalStuff하나의 전역 변수 만 사용하기 위해 큰 클래스를 만들면 코드가 더 명확 해지지 않습니다.


1
승인! 내가 몰랐던 static나는 단지 하나의 함수에 사용되는 변수가있는 경우 그러나 새 값을 함수가 (같은 호출 될 때마다 얻을 var=millis()) 나는 그것을해야을 static?
ATE-ENGE

3
아니요 static. 값을 유지해야 할 때만 사용합니다. 함수에서 가장 먼저 할 일이 변수의 값을 현재 시간으로 설정하면 이전 값을 유지할 필요가 없습니다. 그 상황에서 정적 인 것은 메모리 낭비입니다.
Andrew

이것은 매우 다재다능한 답변으로, 지구에 대한 찬성과 회의론에 대한 두 가지 점을 모두 표현합니다. +1
underscore_d

6

전역 변수의 주요 문제는 코드 유지 관리입니다. 한 줄의 코드를 읽을 때 매개 변수로 전달되거나 로컬로 선언 된 변수의 선언을 쉽게 찾을 수 있습니다. 전역 변수 선언을 찾는 것은 쉽지 않습니다 (종종 필요하고 IDE).

전역 변수가 많으면 (40은 이미 많이 있습니다) 너무 길지 않은 명시적인 이름을 갖는 것이 어려워집니다. 네임 스페이스를 사용하면 전역 변수의 역할을 명확히 할 수 있습니다.

C에서 네임 스페이스를 모방하는 나쁜 방법은 다음과 같습니다.

static struct {
    int motor1, motor2;
    bool sensor;
} arm;

인텔 또는 ARM 프로세서에서 전역 변수에 대한 액세스는 다른 변수보다 느립니다. 아마도 arduino의 반대 일 것입니다.


2
AVR에서 전역은 RAM에있는 반면 대부분의 정적이 아닌 로컬은 CPU 레지스터에 할당되므로 액세스 속도가 빨라집니다.
Edgar Bonet

1
문제는 실제로 선언을 찾는 것이 아닙니다. 코드를 빨리 이해하는 것이 중요하지만 궁극적으로 코드의 기능에 부수적입니다. 실제 문제는 코드의 알 수없는 부분이 전역 선언을 사용 하여 제외 한 객체로 이상한 일을 할 수있는 varmint를 찾는 것입니다 모든 사람이 원하는대로 무엇이든 할 수 있습니다.
underscore_d

5

PC를 프로그래밍 할 때는 사용하지 않지만 Arduino에게는 이점이 있습니다. 이미 언급 된 경우 대부분 :

  • 동적 메모리 사용량 없음 (Arduino의 제한된 힙 공간에 갭 생성)
  • 어디서나 사용할 수 있으므로 인수로 전달할 필요가 없습니다 (스택 공간이 필요함)

또한 일부 경우, 특히 성능 측면에서 필요할 때 요소를 작성하는 대신 전역 변수를 사용하는 것이 좋습니다.

  • 힙 공간의 간격을 줄이려면
  • 동적으로 메모리를 할당 및 / 또는 해제하려면 상당한 시간이 걸릴 수 있습니다
  • 변수는 여러 가지 이유로 사용되는 글로벌 목록의 요소 목록과 같이 '재사용'될 수 있습니다 (예 : SD의 버퍼로 한 번, 나중에 임시 문자열 버퍼로 사용).

5

모든 것 (진정한 악의 고 토스 이외)과 마찬가지로 세계도 그 자리에 있습니다.

예를 들어 디버그 직렬 포트 또는 로그 파일이 있으면 어디에서나 쓸 수 있어야하는 경우 전역으로 만드는 것이 좋습니다. 마찬가지로 중요한 시스템 상태 정보가있는 경우이를 전역 화하는 것이 종종 가장 쉬운 솔루션입니다. 프로그램의 모든 단일 기능에 전달해야하는 값을 가질 필요는 없습니다.

다른 사람들이 말했듯이 46 코드는 1000 줄 이상의 코드에 대해서만 많은 것처럼 보이지만 수행중인 작업에 대한 세부 사항을 알지 못하면 코드를 너무 많이 사용하고 있는지 여부를 말하기가 어렵습니다.

그러나 모든 전 세계 사람들에게 몇 가지 중요한 질문을 해보십시오.

실수로 같은 이름을 다른 곳에서 사용하지 않도록 이름이 명확하고 구체적입니까? 이름을 바꾸지 않으면.

이것이 변경되어야합니까? 그렇지 않다면 그것을 const로 만드는 것을 고려하십시오.

이것은 어디에서나 볼 수 있어야합니까, 아니면 함수 호출간에 값이 유지되도록 전역에서만 가능합니까? 함수에 로컬로 만들고 키워드 static을 사용하는 것이 좋습니다.

내가 조심하지 않을 때 코드 조각으로 변경되면 문제가 심각하게 해결됩니까? 예를 들어 이름과 ID 번호와 같은 두 개의 관련 변수가 전역 인 경우 (거의 모든 곳에서 정보가 필요할 때 전역 사용에 대한 이전 참고 사항 참조) 그 중 하나가 다른 불쾌한 일없이 변경되면 그 일이 발생할 수 있습니다. 그렇습니다. 조심해야하지만 때로는 약간의주의를 기울이는 것이 좋습니다. 예를 들어, 그것들을 다른 .c 파일에 넣은 다음, 둘 다 동시에 설정하고 읽을 수 있도록하는 함수를 정의하십시오. 그런 다음 관련 헤더 파일에 함수 만 포함하면 나머지 코드는 정의 된 함수를 통해서만 변수에 액세스 할 수 있으므로 문제가 발생하지 않습니다.

-update-방금 일반 코딩보다는 Arduino 특정 모범 사례에 대해 물었다는 것을 깨달았습니다. 이는 일반적인 코딩 응답에 가깝습니다. 그러나 솔직히 많은 차이가 없으며 좋은 연습은 좋은 연습입니다. startup()loop()사용할 가지고 아두 이노 수단의 구조는 어떤 상황에서 다른 플랫폼보다 조금 전역하지만 그건 정말 당신은 항상 상관없이 플랫폼의 한계 내에서 할 수있는 최선을 목표로 결국 없으며, 많은 변화하지 않는 것 플랫폼입니다.


Arduino에 대해서는 아무것도 모르지만 많은 데스크톱 및 서버 개발을 수행합니다. 에 대한 하나의 허용 가능한 사용법 (IMHO)이 goto있으며 이는 중첩 루프에서 벗어나기 때문에 대안보다 훨씬 깨끗하고 이해하기 쉽습니다.
지속성

당신 goto이 악 하다고 생각한다면 Linux 코드를 확인하십시오. 논란의 여지가 있지만, 그것들은 try...catch블록 만큼이나 사악 합니다.
Dmitry Grigoryev

5

그들은 악한가? 아마도. 전역의 문제점은 제한없이 실행되는 기능이나 코드로 인해 언제든지 액세스하고 수정할 수 있다는 것입니다. 이로 인해 추적 및 설명이 어려운 상황이 발생할 수 있습니다. 그러므로 가능하면 지구의 양을 최소화하여 그 양을 0으로 되 돌리는 것이 바람직하다.

그들은 피할 수 있습니까? 거의 항상 그렇습니다. Arduino의 문제점은 그들이 당신 setup()과 당신 을 가정하는이 두 가지 기능 접근법으로 당신을 강요한다는 것 loop()입니다. 이 특별한 경우에는이 두 함수의 호출자 함수 범위에 액세스 할 수 없습니다 (아마도 main()). 당신이 있었다면, 당신은 모든 지구촌을 제거하고 대신 지역 주민을 사용할 수 있습니다.

다음을 묘사하십시오 :

int main() {
  setup();

  while (true) {
    loop();
  }
  return 0;
}

이것은 아마도 Arduino 프로그램의 주요 기능과 비슷합니다. 함수 setup()loop()함수 모두에 필요한 변수 main()는 전역 범위가 아닌 함수 범위 내에서 선언되는 것이 좋습니다. 그런 다음 인수로 전달하여 (필요한 경우 포인터 사용) 다른 두 함수에 액세스 할 수 있습니다.

예를 들면 다음과 같습니다.

int main() {
  int myVariable = 0;
  setup(&myVariable);

  while (true) {
    loop(&myVariable);
  }
  return 0;
}

이 경우 두 기능의 서명도 변경해야합니다.

이것이 가능하지 않거나 바람직하지 않을 수 있기 때문에, 강제 프로그램 구조를 수정하지 않고 Arduino 프로그램에서 대부분의 전역을 제거하는 유일한 방법이 있습니다.

내가 올바르게 기억한다면, C 대신 Arduino를 프로그래밍 할 때 C ++을 완벽하게 사용할 수 있습니다. OOP (Object Oriented Programming) 또는 C ++에 익숙하지 않은 경우에는 익숙하지 않을 수 있습니다. 독서.

내 제안은 Program 클래스를 만들고이 클래스의 단일 글로벌 인스턴스를 만드는 것입니다. 클래스는 객체의 청사진으로 간주되어야합니다.

다음 예제 프로그램을 고려하십시오.

class Program {
public:      
  Program();

  void setup();
  void loop();

private:
  int myFirstSampleVariable;
  int mySecondSampleVariable;
};

Program::Program() :
  myFirstSampleVariable(0),
  mySecondSampleVariable(0)
{

}

void Program::setup() {
  // your setup code goes here
}

void Program::loop() {
  // your loop code goes here
}

Program program; // your single global

void setup() {
  program.setup();
}

void loop() {
  program.loop();
}

Voilà, 우리는 거의 모든 세계를 제거했습니다. 애플리케이션 로직 추가를 시작하는 기능 은 Program::setup()and Program::loop()기능입니다. 이 함수는 인스턴스의 특정 멤버 변수에 액세스 할 수 있습니다 myFirstSampleVariablemySecondSampleVariable전통 반면, setup()loop()이러한 변수는 클래스 개인을 표시 한으로 액세스 할 수없는 기능입니다. 이 개념을 데이터 캡슐화 또는 데이터 숨기기 라고 합니다 .

OOP 및 / 또는 C ++을 가르치는 것은이 질문에 대한 답변에서 약간 벗어난 것이므로 여기서 멈출 것입니다.

요약하면 : 지구본을 피해야하며 지구본의 양을 대폭 줄이는 것이 거의 항상 가능합니다. 또한 Arduino를 프로그래밍 할 때도 마찬가지입니다.

가장 중요한 것은 내 대답이 당신에게 다소 유용하기를 바랍니다. :)


원하는 경우 스케치에서 자신의 main ()을 정의 할 수 있습니다. 이것은 주식의 모습입니다 : github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/…
per1234

싱글 톤은 사실상 세계적이며 혼란스러운 방식으로 차려 입었습니다. 같은 단점이 있습니다.
patstew

@patstew 같은 단점이 있다고 생각하십니까? 내 의견으로는 데이터 캡슐화를 이익으로 사용할 수 없기 때문에 그렇지 않습니다.
Arjen

감사합니다! Arduino 전문가는 아니지만 첫 번째 제안도 효과가 있다고 생각합니다.
Arjen

2
글쎄, 그것은 여전히 ​​프로그램의 어느 곳에서나 액세스 할 수있는 전역 상태 Program::instance().setup()입니다 globalProgram.setup(). 대신을 통해 액세스하십시오 . 관련된 전역 변수를 하나의 클래스 / 구조체 / 네임 스페이스에 넣는 것이 특히 유용합니다. 특히 두 개의 관련 함수에서만 필요하지만 싱글 톤 패턴으로 아무것도 추가하지 않는 경우에 특히 그렇습니다. 다시 말해, static Program p;글로벌 스토리지와 static Program& instance()글로벌 액세스 권한이 있으며 이는 단순히 다음과 같습니다 Program globalProgram;.
patstew

4

전역 변수는 결코 사악 하지 않습니다 . 그들에 대한 일반적인 규칙은 더 나은 결정을 내릴 수있는 경험을 얻을 수있을만큼 오래 살아남 게하는 목발입니다.

전역 변수 란 무엇인가 하나만 존재한다는 고유의 가정입니다 (여러 항목을 포함 할 수있는 전역 배열 또는 맵에 대해 이야기하고 있는지 여부는 중요하지 않음) 하나의 그러한 목록 또는 매핑 (여러 독립적 인 목록이 아님).

그래서 당신이 글로벌을 사용하기 전에, 당신은 스스로에게 묻기를 원합니다 : 내가이 것을 하나 이상 사용하고 싶을 것입니까? 그것이 사실이라고 판명되면 그 코드를 세계화하지 않기 위해 코드를 수정해야하며 코드의 다른 부분이 고유성 가정에 의존하는 방식을 따라야 할 것입니다. 또한 수정해야하며 프로세스가 지루하고 오류가 발생하기 쉽습니다. "글로벌을 사용하지 마십시오"는 일반적으로 처음부터 글로벌을 피하는 데 비용이 거의 들지 않으며 나중에 큰 비용을 지불 할 가능성을 피하기 때문입니다.

그러나 전역이 허용한다는 간단한 가정은 코드를 더 작고 빠르며 메모리를 적게 사용합니다. 코드는 사용하는 것에 대한 개념을 전달할 필요가 없으며 간접적으로 할 필요가 없으며 반드시 할 필요가 없기 때문입니다. 원하는 것이 존재하지 않을 가능성 등을 고려하십시오. 임베디드에서는 PC보다 코드 크기 및 / 또는 CPU 시간 및 / 또는 메모리가 제한 될 가능성이 높으므로 이러한 비용 절감이 중요 할 수 있습니다. 그리고 많은 임베디드 애플리케이션은 요구 사항에 더 강성이 - 당신은 알고 사용자가 그냥 USB 포트 또는 뭔가에 다른 하나를 연결하지 수, 당신의 칩은 특정 주변 장치 중 하나가있다.

독창적 인 것으로 보이는 하나 이상의 것을 원하는 또 다른 일반적인 이유는 테스트입니다. 일부 구성 요소의 테스트 인스턴스를 함수에 전달할 수있을 때 전역 구성 요소의 동작을 수정하려고하면 두 구성 요소 간의 상호 작용을 테스트하는 것이 더 쉽습니다. 까다로운 제안. 그러나 임베디드 세계에서의 테스트는 다른 곳과는 매우 다른 경향이 있으므로 적용되지 않을 수 있습니다. 내가 아는 한 Arduino에는 테스트 문화가 없습니다.

가치있는 것처럼 보이면 글로벌을 사용하십시오. 코드 경찰이 와서 당신을 얻을 수 없습니다. 잘못 선택하면 길에서 더 많은 일을 할 수 있으므로 잘 모르는 경우 ...


0

Arduino에서 전역 변수가 악한가?

전역 변수를 포함하여 본질적으로 악한 것은 없습니다. 나는 그것을 "필요한 악"으로 특징 지을 것입니다-그것은 당신의 인생을 훨씬 더 쉽게 만들 수 있지만주의해서 접근해야합니다.

또한 세계의 수를 더 줄이기 위해 어떤 관행을 사용할 수 있습니까?

랩퍼 함수를 ​​사용하여 글로벌 변수에 액세스하십시오. 최소한 스코프 관점에서 관리하고 있습니다.


3
랩퍼 함수를 ​​사용하여 글로벌 변수에 액세스하는 경우 변수를 이러한 함수 안에 넣을 수도 있습니다.
Dmitry Grigoryev
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.