코딩 기본 사항
애플리케이션 코드가 이 항목에 정의된 최소 품질 표준을 충족하는 것이 좋습니다. 프로덕션 배포 애플리케이션을 개선하려는 고객과의 파트너십을 통해 애플리케이션 성능을 개선하는 몇 가지 일반적인 문제를 발견했습니다.
일반적인 문제
- 대상 API 집합을 설정할 때는 최신 CMake 및 Azure Sphere 도구를 사용하고 를 설정
AZURE_SPHERE_TARGET_API_SET="latest-lts"
하여 최종 릴리스 이진 파일을 컴파일하는 것이 좋습니다. 자세한 내용은 재생 가능 보안을 위한 코딩을 참조하세요.
참고
제조 프로세스 내에서 테스트용으로 로드할 이미지 패키지를 특별히 만들 때 디바이스를 원본으로 사용하거나 복구한 적절한 Azure Sphere OS 버전으로 설정합니다 AZURE_SPHERE_TARGET_API_SET
. 이렇게 하지 않으면 Azure Sphere OS가 이미지 패키지를 거부하게 됩니다.
- 프로덕션에 애플리케이션을 배포할 준비가 되면 릴리스 모드에서 최종 이미지 패키지를 컴파일해야 합니다.
- 컴파일러 경고에도 불구하고 프로덕션에 배포된 애플리케이션을 보는 것이 일반적입니다. 전체 빌드에 대해 제로 경고 정책을 적용하면 모든 컴파일러 경고가 의도적으로 해결됩니다. 다음은 가장 자주 발생하는 경고 유형이며, 이를 해결하는 것이 좋습니다.
- 암시적 변환 관련 경고: 초기 빠른 구현으로 인해 수정되지 않은 상태로 남아 있는 암시적 변환으로 인해 버그가 도입되는 경우가 많습니다. 예를 들어 여러 숫자 형식 간에 암시적 숫자 변환이 많은 코드는 심각한 정밀도 손실 또는 계산 또는 분기 오류를 초래할 수 있습니다. 모든 숫자 형식을 올바르게 조정하려면 캐스팅뿐만 아니라 의도적인 분석과 캐스팅을 모두 사용하는 것이 좋습니다.
- 예상되는 매개 변수 형식을 변경하지 않도록 합니다. API를 호출할 때 명시적으로 캐스팅하지 않으면 암시적 변환으로 인해 문제가 발생할 수 있습니다. 예를 들어 부호 없는 숫자 형식 대신 부호 있는 숫자 형식을 사용할 때 버퍼를 오버런합니다.
- const-discarding 경고: 함수에 const 형식이 매개 변수로 필요한 경우 이를 재정의하면 버그와 예측할 수 없는 동작이 발생할 수 있습니다. 경고의 이유는 const 매개 변수가 그대로 유지되고 특정 API 또는 함수를 디자인할 때 제한을 고려하기 위한 것입니다.
- 호환되지 않는 포인터 또는 매개 변수 경고: 이 경고를 무시하면 나중에 추적하기 어려운 버그가 숨겨질 수 있습니다. 이러한 경고를 제거하면 다른 애플리케이션 문제의 진단 시간을 줄일 수 있습니다.
- 일관된 CI/CD 파이프라인 설정은 이전 애플리케이션 릴리스를 디버깅하기 위해 이진 파일 및 해당 특파원 기호를 쉽게 다시 빌드할 수 있으므로 지속 가능한 장기 애플리케이션 관리의 핵심입니다. 적절한 분기 전략은 릴리스를 추적하는 데도 필수적이며 이진 데이터를 저장하는 데 비용이 많이 드는 디스크 공간을 방지합니다.
메모리 관련 문제
- 가능하면 코드를 유지 관리하는 동시에 전체 코드베이스에서 데이터 포인터로
global const char*
사용할 수 있도록 모든 공통 고정 문자열을 하드 코딩하는 대신(예: 명령 내에서printf
) 로 정의합니다. 실제 애플리케이션에서 로그 또는 문자열 조작(예: ,Succeeded
또는 JSON 속성 이름)에서 일반 텍스트를 수집하고 상수로OK
전역화하면 읽기 전용 데이터 메모리 섹션(.rodata라고도 함)이 절약되는 경우가 많으며, 이는 다른 섹션에서 사용할 수 있는 플래시 메모리의 절감(예: 더 많은 코드의 경우 .text)으로 변환됩니다. 이 시나리오는 종종 간과되지만 플래시 메모리를 크게 절감할 수 있습니다.
참고
위의 작업은 단순히 컴파일러 최적화(예: -fmerge-constants
gcc)를 활성화하여 수행할 수도 있습니다. 이 방법을 선택하는 경우 컴파일러 출력을 검사하고 원하는 최적화가 적용되었는지 확인합니다. 이러한 최적화는 다른 컴파일러 버전에 따라 달라질 수 있습니다.
- 전역 데이터 구조의 경우 가능하면 동적으로 할당된 메모리에 대한 포인터를 사용하는 대신 합리적으로 작은 배열 멤버에 고정 길이를 제공하는 것이 좋습니다. 예를 들어:
typedef struct {
int chID;
...
char chName[SIZEOF_CHANNEL_NAME]; // This approach is preferable, and easier to use e.g. in a function stack.
char *chName; // Unless this points to a constant, tracking a memory buffer introduces more complexity, to be weighed with the cost/benefit, especially when using multiple instances of the structure.
...
} myConfig;
- 특히 자주 호출되는 함수 내에서 가능하면 동적 메모리 할당을 방지합니다.
- C에서 메모리 버퍼에 대한 포인터를 반환하는 함수를 찾아 참조된 버퍼 포인터와 관련 크기를 호출자에게 반환하는 함수로 변환하는 것이 좋습니다. 이 작업을 수행하는 이유는 반환된 버퍼의 크기가 강제로 승인되지 않으므로 힙의 일관성을 위험에 빠뜨릴 수 있으므로 버퍼에 대한 포인터만 반환하면 호출 코드에 문제가 발생하는 경우가 많기 때문입니다. 예를 들어:
// This approach is preferable:
MY_RESULT_TYPE getBuffer(void **ptr, size_t &size, [...other parameters..])
// This should be avoided, as it lacks tracking the size of the returned buffer and a dedicated result code:
void *getBuffer([...other parameters..])
동적 컨테이너 및 버퍼
목록 및 벡터와 같은 컨테이너는 포함된 C 애플리케이션에서도 자주 사용되며, 표준 라이브러리 사용 시 메모리 제한으로 인해 일반적으로 명시적으로 코딩되거나 라이브러리로 연결되어야 한다는 점을 주의해야 합니다. 이러한 라이브러리 구현은 신중하게 설계되지 않은 경우 집중적인 메모리 사용을 트리거할 수 있습니다.
일반적인 정적으로 할당된 배열 또는 매우 메모리 동적 구현 외에도 증분 할당 방법을 사용하는 것이 좋습니다. 예를 들어 미리 할당된 N 개체의 빈 큐 구현으로 시작합니다. (N+1)th 큐 푸시에서 큐는 고정 X 추가 미리 할당된 개체(N=N+X)에 의해 증가하며, 큐에 다른 추가 항목이 현재 용량을 오버플로하고 X 추가 미리 할당된 개체로 메모리 할당을 증가시킬 때까지 동적으로 할당된 상태로 유지됩니다. 결국 새 압축 함수를 구현하여 사용하지 않는 메모리를 회수하기 위해 자주 호출할 수 있습니다(정기적으로 호출하는 데 너무 비싸기 때문에).
전용 인덱스는 큐의 활성 개체 수를 동적으로 유지하며, 이는 추가 오버플로 보호를 위해 최대값으로 제한될 수 있습니다.
이 방법은 기존 큐 구현에서 지속적인 메모리 할당 및 할당 취소로 인해 생성된 "잡담"을 제거합니다. 자세한 내용은 메모리 관리 및 사용을 참조하세요. 목록, 배열 등과 같은 구조체에 대해 유사한 접근 방식을 구현할 수 있습니다.