한 줄의 코드를 함수 또는 서브 루틴으로 감싸서 내가 생각할 수있는 이점은 없습니다 (그러나 JasonS에 대한 참고 사항 참조). 함수 이름을 "읽기 쉬운"것으로 지정할 수있는 경우를 제외하고. 그러나 당신은 라인을 주석 처리 할 수 있습니다. 함수에 코드 줄을 정리하면 코드 메모리, 스택 공간 및 실행 시간이 들기 때문에 대부분 비생산적인 것 같습니다 . 교육 상황에서? 말이 될 수도 있습니다. 그러나 그것은 학생들의 수업, 사전 준비, 커리큘럼 및 교사에 달려 있습니다. 대부분 좋은 생각이 아니라고 생각합니다. 그러나 그것은 나의 의견이다.
결론에 이르게됩니다. 당신의 광범위한 질문 영역은 수십 년 동안 논쟁의 문제였으며 오늘날까지도 여전히 논쟁의 문제입니다. 따라서 적어도 귀하의 질문을 읽을 때 (당신이 질문 한대로) 의견 기반의 질문 인 것 같습니다.
상황에 대해 더 자세하게 설명하고 기본 목표로 세운 목표를주의 깊게 설명한다면, 그것은 의견에 근거한 것에서 벗어날 수 있습니다. 측정 도구를 잘 정의할수록 대답이 더 객관적 일 수 있습니다.
일반적으로 모든 코딩에 대해 다음을 수행하려고합니다 . (아래에서는 목표를 달성하는 모든 접근 방식을 비교한다고 가정합니다. 분명히 필요한 작업을 수행하지 못하는 코드는 작성 방법에 관계없이 성공한 코드보다 나쁩니다.)
- 코드를 읽는 다른 사람이 코딩 프로세스에 어떻게 접근하는지 이해하도록 접근 방식에 일관성을 유지하십시오. 일관성이없는 것은 아마도 최악의 범죄 일 것입니다. 그것은 다른 사람들을 어렵게 할뿐만 아니라 몇 년 후 코드로 돌아가는 것을 어렵게 만듭니다.
- 가능한 한, 순서에 관계없이 다양한 기능 섹션의 초기화를 수행 할 수 있도록 사물을 배열하십시오. 순서가 필요한 경우, 관련성이 높은 두 개의 하위 기능 이 밀접하게 결합 되어 있으면 두 가지 모두에 대해 단일 초기화를 고려하여 피해를주지 않고 순서를 다시 지정할 수 있습니다. 이것이 가능하지 않은 경우 초기화 순서 요구 사항을 문서화하십시오.
- 캡슐화 지식가능한 경우 정확히 한 곳에 을 하십시오. 상수는 코드의 모든 곳에서 복제되어서는 안됩니다. 일부 변수를 해결하는 방정식은 한 곳에만 존재해야합니다. 등등. 다양한 위치에서 필요한 동작을 수행하는 일부 행 집합을 복사하여 붙여 넣는 경우 해당 지식을 한 곳에서 캡처하여 필요한 곳에서 사용하는 방법을 고려하십시오. 특정 방식으로 걸어해야 트리 구조를 가지고 예를 들어, 할 수 없습니다 은 트리 노드를 순환해야하는 모든 장소에서 트리 워킹 코드를 복제합니다. 대신, 트리 워킹 방법을 한 곳에서 캡처하여 사용하십시오. 이런 식으로, 나무가 바뀌고 보행 방법이 바뀌면 걱정할 곳이 하나 밖에 없으며 나머지 코드는 모두 "정상 작동"합니다.
- 모든 루틴을 거대한 평평한 종이에 펼치고 화살표가 다른 루틴에 의해 호출되는 것처럼 연결하면 모든 응용 프로그램에서 화살표가 많은 루틴의 "클러스터"가 있음을 알 수 있습니다 그들 사이에 있지만 그룹 바깥에 몇 개의 화살 만 있습니다. 그래서이있을 것이다 자연 밀접하게 결합 된 루틴의 경계와 밀접하게 결합 된 루틴의 다른 그룹과 느슨하게 결합 된 연결을. 이 사실을 사용하여 코드를 모듈로 구성하십시오. 이렇게하면 코드의 복잡성이 크게 줄어 듭니다.
위의 내용은 일반적으로 모든 코딩에 적용됩니다. 매개 변수, 로컬 또는 정적 전역 변수 등의 사용에 대해서는 논의하지 않았습니다. 그 이유는 임베디드 프로그래밍의 경우 응용 프로그램 공간이 종종 극도로 매우 중요한 새로운 제약 조건을 배치하기 때문에 모든 임베디드 응용 프로그램에 대해 논의하지 않고 모든 제약 조건을 논의 할 수 없기 때문입니다. 그리고 어쨌든 여기서 일어나지 않습니다.
이러한 제약 조건은 다음 중 하나 이상일 수 있습니다.
- 최소 RAM과 거의 I / O 핀 수를 갖지 않는 매우 원시적 인 MCU를 요구하는 심각한 비용 제한. 이를 위해 완전히 새로운 규칙이 적용됩니다. 예를 들어 코드 공간이 많지 않으므로 어셈블리 코드로 작성해야 할 수 있습니다. 로컬 변수를 사용하면 비용이 많이 들고 시간이 많이 걸리므로 정적 변수 만 사용해야 할 수도 있습니다. 서브 루틴 리턴 주소를 저장할 하드웨어 레지스터가 4 개뿐이므로 (예 : 일부 Microchip PIC 부품) 서브 루틴을 과도하게 사용하지 않아야 할 수도 있습니다. 따라서 코드를 극적으로 "평평하게"해야 할 수도 있습니다. 기타.
- 대부분의 MCU를 시작하고 종료하기 위해 정교하게 제작 된 코드가 필요하고 최대 속도로 실행할 때 코드 실행 시간에 심각한 제한이있는 심각한 전원 제한. 다시 말하지만 때때로 일부 어셈블리 코딩이 필요할 수 있습니다.
- 심각한 타이밍 요구 사항. 예를 들어, 오픈 드레인 0의 전송이 정확히 1의 전송과 동일한 수의 사이클을 취해야하는지 확인해야 할 때가 있습니다. 그리고 동일한 라인을 샘플링해야했습니다. 이 타이밍에 대한 정확한 상대 단계. 이것은 여기서 C를 사용할 수 없다는 것을 의미했습니다. 보장 할 수있는 유일한 방법은 어셈블리 코드를 신중하게 작성하는 것입니다. (그리고 심지어 모든 ALU 디자인에서 항상 그런 것은 아닙니다.)
등등. 생명에 중요한 의료 기기의 배선 코드는 전 세계에 있습니다.
여기서 결론은 임베디드 코딩이 종종 자유롭지 않다는 것입니다. 워크 스테이션 에서처럼 코딩 할 수 있습니다. 매우 다양한 제약 조건에 대한 심각하고 경쟁적인 이유가 종종 있습니다. 그리고 이것들은 더 전통적인 답변 과 주식 답변 에 강력하게 반대 할 수 있습니다 .
가독성과 관련하여 읽을 때 배울 수있는 일관된 방식으로 작성된 코드는 읽을 수있는 것으로 나타났습니다. 그리고 코드를 난독 화하려는 의도적 인 시도가없는 곳. 더 이상 필요하지 않습니다.
읽을 수있는 코드는 매우 효율적일 수 있으며 위에서 언급 한 위의 모든 요구 사항을 충족 할 수 있습니다 . 가장 중요한 것은 작성하는 각 코드 줄이 코드를 작성할 때 어셈블리 또는 기계 수준에서 생성되는 내용을 완전히 이해한다는 것입니다. C ++는 많은 상황이 있기 때문에 여기 프로그래머에 심각한 부담을 동일한 C ++ 코드의 조각이 실제로 생성 다른 매우 다른 공연을 기계 코드의 조각이. 그러나 C는 일반적으로 "보이는 것이 얻는 것"입니다. 따라서 그 점에서 더 안전합니다.
JasonS 당 편집 :
1978 년부터 C를 사용했고 1987 년부터 C ++을 사용해 왔으며 메인 프레임, 미니 컴퓨터 및 (대부분) 임베디드 응용 프로그램 모두에 대해 많은 경험을 가지고 있습니다.
Jason은 '인라인'을 수정 자로 사용하는 것에 대한 의견을 제시합니다. (내 관점에서 볼 때 이것은 C 및 C ++를 사용하여 내 인생의 절반 이상을 위해 존재하지 않았기 때문에 비교적 "새로운"기능입니다.) 인라인 함수를 사용하면 실제로 이러한 호출을 할 수 있습니다 (한 줄의 경우에도 코드) 아주 실용적입니다. 그리고 컴파일러가 적용 할 수있는 타이핑 때문에 매크로를 사용하는 것보다 가능하면 훨씬 좋습니다.
그러나 한계도 있습니다. 첫 번째는 "힌트를 가져 오기 위해"컴파일러에 의존 할 수 없다는 것입니다. 그럴 수도 있고 아닐 수도 있습니다. 그리고 힌트를 얻지 않는 좋은 이유가 있습니다. (함수의 주소를 가지고가는 경우에 명백한 예를 들어,이 요구 함수의 인스턴스 및 ... 전화를 필요로 전화를 걸 수있는 주소의 사용을. 코드는 다음 인라인 할 수 없습니다.)이있다 다른 이유도 있습니다. 컴파일러는 힌트를 처리하는 방법을 판단하는 다양한 기준을 가질 수 있습니다. 그리고 프로그래머로, 이것은 당신을 의미 한다컴파일러의 측면에 대해 배우거나 결함이있는 아이디어를 바탕으로 결정을 내릴 가능성이 있습니다. 따라서 코드 작성자와 독자 및 코드를 다른 컴파일러로 이식하려는 모든 사람에게 부담이됩니다.
또한 C 및 C ++ 컴파일러는 별도의 컴파일을 지원합니다. 즉, 프로젝트의 다른 관련 코드를 컴파일하지 않고도 C 또는 C ++ 코드를 컴파일 할 수 있습니다. 코드를 인라인하려면 컴파일러가 달리 선택한다고 가정하면 "범위 내에"선언이 있어야 할뿐만 아니라 정의도 있어야합니다. 일반적으로 프로그래머는 '인라인'을 사용하는 경우 이러한 상황이 발생하도록 노력할 것입니다. 그러나 실수가 발생하기 쉽습니다.
일반적으로 적절하다고 생각되는 곳에서 인라인을 사용하지만 의존 할 수 없다고 가정하는 경향이 있습니다. 성능이 중요한 요구 사항이고 OP가 이미 더 "기능적인"경로로 갈 때 상당한 성능 저하가 발생했다고 분명하게 기록한 경우 인라인을 코딩 방식으로 사용하지 않는 것이 좋습니다. 대신 약간 다르지만 완전히 일관된 코드 작성 패턴을 따릅니다.
'인라인'과 별도의 컴파일 단계에서 "범위 내"인 정의에 대한 마지막 참고 사항. 연결 단계에서 작업을 수행 할 수 있습니다 (항상 신뢰할 수있는 것은 아님). 링커가 '인라인'요청에 대해 작동 할 수 있도록 C / C ++ 컴파일러가 오브젝트 파일에 충분한 세부 사항을 묻은 경우에만 발생할 수 있습니다. 개인적으로이 기능을 지원하는 링커 시스템 (Microsoft 외부)은 경험하지 못했습니다. 그러나 발생할 수 있습니다. 다시 말하지만, 의존해야하는지 여부는 상황에 따라 다릅니다. 그러나 나는 좋은 증거를 기반으로 달리 알지 않는 한 링커에 삽질되지 않았다고 생각합니다. 그리고 내가 그것에 의존한다면, 그것은 눈에 띄는 곳에 문서화 될 것입니다.
C ++
관심있는 사람들을 위해 여기에 현재 준비되어 있음에도 불구하고 임베디드 응용 프로그램을 코딩 할 때 C ++에 대해 신중한 이유가 있습니다. 모든 임베디드 C ++ 프로그래머가 차가운 것을 알아야 한다고 생각하는 용어를 던져 보겠습니다 .
- 부분 템플릿 전문화
- vtables
- 가상 기본 개체
- 활성화 프레임
- 활성화 프레임 풀기
- 생성자에서 스마트 포인터 사용 및 이유
- 반환 값 최적화
그것은 짧은 목록 일뿐입니다. 해당 용어에 대한 모든 정보와 내가 왜 나열했는지 (그리고 여기에 나열하지 않은 많은 것들)를 모르는 경우 프로젝트에 대한 옵션이 아닌 한 임베디드 작업에 C ++을 사용하지 않는 것이 좋습니다. .
C ++ 예외 시맨틱을 간단히 살펴보면 맛을 볼 수 있습니다.
ㅏ비
ㅏ
.
.
foo ();
String s;
foo ();
.
.
ㅏ
비
C ++ 컴파일러는 foo ()에 대한 첫 번째 호출을보고 foo ()에서 예외가 발생하는 경우 정상적인 활성화 프레임 풀기를 허용 할 수 있습니다. 다시 말해, C ++ 컴파일러는이 시점에서 예외 처리와 관련된 프레임 해제 프로세스를 지원하기 위해 추가 코드가 필요하지 않다는 것을 알고 있습니다.
그러나 일단 String이 생성되면 C ++ 컴파일러는 나중에 예외가 발생할 경우 프레임 해제가 허용되기 전에 올바르게 파괴되어야한다는 것을 알고 있습니다. 따라서 foo ()에 대한 두 번째 호출은 의미 적으로 첫 번째와 다릅니다. foo ()에 대한 두 번째 호출에서 예외가 발생하거나 예외가 발생하는 경우 컴파일러는 일반적인 프레임이 풀리기 전에 String의 소멸을 처리하도록 설계된 코드를 배치해야합니다. 이것은 foo ()에 대한 첫 번째 호출에 필요한 코드 와 다릅니다 .
( 이 문제를 제한하기 위해 C ++에 추가 데코레이션 을 추가 할 수 있습니다 . 그러나 실제로 C ++을 사용하는 프로그래머는 작성하는 각 코드 줄의 의미를 훨씬 더 잘 알고 있어야합니다.)
C의 malloc과 달리 C ++의 새로운 기능은 예외를 사용하여 원시 메모리 할당을 수행 할 수 없을 때 신호를 보냅니다. 'dynamic_cast'도 마찬가지입니다. C ++의 표준 예외에 대해서는 Stroustrup의 3 번째 에디션, C ++ 프로그래밍 언어, 384 및 385 페이지를 참조하십시오. 컴파일러는이 동작을 비활성화 할 수 있습니다. 그러나 일반적으로 예외가 실제로 발생하지 않고 컴파일되는 함수에 실제로 예외 처리 블록이없는 경우에도 생성 된 코드에서 올바르게 처리 된 예외 처리 프롤로그 및 에필로그로 인해 약간의 오버 헤드가 발생합니다. (Stroustrup은 이것을 공개적으로 애도했습니다.)
부분적인 템플릿 전문화 (모든 C ++ 컴파일러에서 지원하지는 않음)가 없으면 템플릿을 사용하면 임베디드 프로그래밍의 재앙이 발생할 수 있습니다. 이것이 없으면 코드 블룸은 심각한 메모리 내장 프로젝트를 플래시로 죽일 수있는 심각한 위험입니다.
C ++ 함수가 객체를 반환하면 명명되지 않은 컴파일러 임시 파일이 만들어지고 소멸됩니다. 일부 C ++ 컴파일러는 객체 생성자가 로컬 객체 대신 return 문에 사용되는 경우 효율적인 코드를 제공하여 하나의 객체로 구성 및 소멸 요구를 줄입니다. 그러나 모든 컴파일러가이 작업을 수행하는 것은 아니며 많은 C ++ 프로그래머는 이러한 "반환 값 최적화"를 인식하지 못합니다.
단일 매개 변수 유형으로 객체 생성자를 제공하면 C ++ 컴파일러가 프로그래머에게 전혀 예상치 못한 방식으로 두 유형 간의 변환 경로를 찾을 수 있습니다. 이런 종류의 "똑똑한"행동은 C의 일부가 아닙니다.
기본 유형을 지정하는 catch 절은 던져진 객체가 객체의 "동적 유형"이 아니라 catch 절의 "정적 유형"을 사용하여 복사되므로 던져진 파생 객체를 "슬라이스"합니다. 예외적 인 불행의 원인은 아닙니다 (내장 코드에서 예외를 감당할 수 있다고 생각 될 때).
C ++ 컴파일러는 의도하지 않은 결과로 생성자, 소멸자, 복사 생성자 및 할당 연산자를 자동으로 생성 할 수 있습니다. 자세한 내용은 시설을 확보하는 데 시간이 걸립니다.
파생 객체의 배열을 기본 객체의 배열을 허용하는 함수에 전달하면 컴파일러 경고가 거의 생성되지 않지만 거의 항상 잘못된 동작이 발생합니다.
C ++는 객체 생성자에서 예외가 발생할 때 부분적으로 생성 된 객체의 소멸자를 호출하지 않기 때문에 생성자에서 예외를 처리하면 예외가 발생하는 경우 생성자의 생성 된 조각이 제대로 파괴되도록하기 위해 일반적으로 "스마트 포인터"가 필요합니다. . (367 페이지 및 368 페이지 Stroustrup 참조) 이는 C ++로 좋은 클래스를 작성하는 데 일반적으로 발생하는 문제이지만 C에는 생성 및 소멸의 시맨틱이 내장되어 있지 않으므로 C에서는 피할 수 있습니다. 객체 내 하위 객체의 의미는 C ++에서이 고유 한 의미 론적 문제에 대처해야하는 코드 작성을 의미합니다. 다시 말해서 C ++ 시맨틱 동작을 "쓰기"입니다.
C ++는 객체 매개 변수에 전달 된 객체를 복사 할 수 있습니다. 예를 들어 다음 조각에서는 "rA (x);" C ++ 컴파일러가 매개 변수 p에 대한 생성자를 호출하도록 할 수 있습니다. 복사 생성자를 호출하여 객체 x를 매개 변수 p로 전송 한 다음 함수 rA의 반환 객체 (명명되지 않은 임시)에 대한 다른 생성자입니다. 파라미터 p에서 복사. 더구나 A 급에 건설이 필요한 자체 물체가 있다면 이것은 망원경으로 재앙을 일으킬 수있다. (AC 프로그래머는 C 프로그래머가 편리한 구문을 가지고 있지 않고 한 번에 하나씩 모든 세부 사항을 표현해야하기 때문에이 쓰레기를 대부분 피하고 수동으로 최적화합니다.)
class A {...};
A rA (A p) { return p; }
// .....
{ A x; rA(x); }
마지막으로 C 프로그래머를위한 짧은 메모입니다. longjmp ()는 C ++에서 이식 가능한 동작이 없습니다. (일부 C 프로그래머는 이것을 일종의 "예외"메커니즘으로 사용합니다.) 일부 C ++ 컴파일러는 실제로 longjmp를 가져올 때 정리하도록 설정하려고 시도하지만 C ++에서는 이러한 동작을 이식 할 수 없습니다. 컴파일러가 생성 된 객체를 정리하면 이식 할 수 없습니다. 컴파일러가 정리하지 않으면 코드가 longjmp의 결과로 생성 된 객체의 범위를 벗어나고 동작이 잘못된 경우 객체가 소멸되지 않습니다. (foo ()에서 longjmp를 사용하여 범위를 벗어나지 않으면 동작이 문제가되지 않을 수 있습니다.) 이것은 C 임베디드 프로그래머가 너무 자주 사용하지는 않지만 사용하기 전에 이러한 문제를 스스로 알아야합니다.