CAPL 스크립트 배열 조작 실전: 효율적인 검색 및 부배열 매칭 기술

1. 왜 배열 검색은 CAPL 스크립트의 필수 기술인가?

CANoe를 사용하여 버스 테스트나 진단 작업을 수행한 경험이 있다면 CAPL 스크립트와 많이 접해보셨을 것입니다. 저는 자동차 전자 분야에서 오랫동안 일해왔는데, 많은 초보 개발자들이 배열 조작, 특히 대용량 데이터에서 특정 값을 찾거나 특정 데이터 시퀀스(부배열)를 확인해야 할 때 막막함을 느낍니다. 결과적으로는 비효율적인 반복문을 작성하게 되고, 스크립트 실행 속도가 느려지며, 로직 오류가 발생하고 디버깅에 많은 시간이 소요됩니다.

실제로 배열의 검색과 매칭은 CAPL 스크립트에서 가장 기본적이면서도 실용적인 작업 중 하나입니다. 예를 들어, 버스로부터 수신한 데이터 프레임(바이트 배열)에서 특정 고장 코드(DTC)가 포함되어 있는지 확인해야 할 수도 있고, ECU의 긍정 응답 서비스 데이터가 예상된 형식과 내용과 일치하는지 검증해야 할 수도 있습니다. 또는, 연속적으로 수집된 신호 데이터 스트림에서 특정 패턴을 찾아내야 할 수도 있습니다. 이 모든 작업은 본질적으로 배열에서 '무언가를 찾는 것'입니다.

검색 로직을 명확하고 효율적인 함수로 캡슐화하는 것은 스크립트 개발 효율성과 실행 안정성을 높이는 핵심입니다. 오늘은 저의 경험과 실제 적용 사례를 바탕으로, CAPL에서 배열을 검색하는 몇 가지 실전 기술을 상세히 설명하겠습니다. 가장 간단한 단일 값 검색부터, 조금 더 복잡한 다중 값 검색, 그리고 알고리즘을 시험하는 부배열 매칭에 이르기까지, 성능 최적화 방법까지 다룰 것입니다. 모두 바로 사용할 수 있는 코드를 제공하며, 코드 뒤의 '왜'에 대한 설명도 포함하여 단순히 사용법을 넘어 깊이 이해할 수 있도록 돕겠습니다.

2. 기본기: 배열에서 단일 값 정확히 찾기

가장 간단한 경우부터 시작해 보겠습니다. `source`라는 바이트 배열이 있고, 그 안에 특정 값 `target`이 있는지 확인하고, 있다면 처음 나타나는 위치(인덱스)를 알고 싶다고 가정해 보겠습니다. 이러한 요구 사항은 DTC 목록에서 특정 DTC를 찾는 것과 같이 매우 흔하게 발생합니다.

가장 직관적인 방법은 `for` 루프를 사용하여 배열을 처음부터 끝까지 순회하는 것입니다. 맞습니다, 이것이 바로 알고리즘에서 말하는 '선형 검색(Linear Search)'입니다. 하지만 어떻게 더 우아하고 재사용 가능하게 작성할 수 있을까요? 저는 이를 함수로 캡슐화하는 것을 선호합니다.

2.1 선형 검색 함수 직접 구현하기

먼저 코드를 살펴보겠습니다. 저가 자주 사용하는 검색 함수입니다:

/* 원본 배열(dataBuffer)에서 특정 바이트(searchByte)를 검색합니다.
 * 값을 찾으면 해당 인덱스를 반환하고, 찾지 못하면 -1을 반환합니다.
 */
int findSingleByteInArray(byte searchByte, byte dataBuffer[])
{
  int currentIndex;
  for(currentIndex = 0; currentIndex < elCount(dataBuffer); currentIndex++)
  {
    if(dataBuffer[currentIndex] == searchByte)
    {
      // 디버깅을 위해 출력 (실제 스크립트에서는 주석 처리할 수 있음)
      write("값 %d이(가) 인덱스 %d에서 발견되었습니다.", searchByte, currentIndex);
      return currentIndex; // 값을 찾으면 즉시 인덱스를 반환합니다.
    }
  }
  return -1; // 배열을 모두 검색했지만 값이 없으면 -1을 반환합니다.
}

이 함수의 주요 포인트를 분석해 보겠습니다:

  1. 매개변수: 찾고자 하는 `searchByte`(단일 값)와 검색 대상이 되는 `dataBuffer` 배열이 명확하게 정의되어 있습니다.
  2. 반복문: `for(currentIndex = 0; currentIndex < elCount(dataBuffer); currentIndex++)`를 사용합니다. `elCount()` 함수를 사용하여 배열의 길이를 가져오면, 배열 크기가 변경되어도 코드를 수정할 필요가 없어 더 안전합니다.
  3. 비교 및 반환: 반복문 내에서 값을 하나씩 비교합니다. 값이 발견되면 즉시 `return currentIndex`를 사용하여 인덱스를 반환합니다. 이것이 가장 효율적인 방법입니다.
  4. 값을 찾지 못한 경우: 반복문이 완전히 실행되었음에도 값이 없으면, 함수는 마지막에 `return -1`을 실행합니다. 인덱스는 음수가 될 수 없으므로, '-1'을 '찾지 못함'을 의미하는 프로그래밍에서의 일반적인 약속으로 사용합니다.

2.2 실전 호출 및 오류 해결

이 함수는 어떻게 사용할까요? 아래의 테스트 예제를 살펴보겠습니다:

on start // 스크립트 시작 시 테스트를 실행합니다.
{
  byte testData[10] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x10, 0x20};
  byte targetValue = 0x67;
  int resultPosition;

  resultPosition = findSingleByteInArray(targetValue, testData);

  if(resultPosition != -1)
  {
    write("성공! 값 0x%02X는 배열의 인덱스 %d에 위치합니다.", targetValue, resultPosition);
  }
  else
  {
    write("실패! 값 0x%02X는 배열에 존재하지 않습니다.", targetValue);
  }
}

태그: CAPL 배열 선형 검색 CANoe 자동차 전자

7월 28일 15:12에 게시됨