View in English

  • 메뉴 열기 메뉴 닫기
  • Apple Developer
검색
검색 닫기
  • Apple Developer
  • 뉴스
  • 둘러보기
  • 디자인
  • 개발
  • 배포
  • 지원
  • 계정
페이지에서만 검색

빠른 링크

5 빠른 링크

비디오

메뉴 열기 메뉴 닫기
  • 컬렉션
  • 주제
  • 전체 비디오
  • 소개

더 많은 비디오

  • 소개
  • 요약
  • 자막 전문
  • 코드
  • C, C++, Swift를 안전하게 함께 사용하기

    앱의 안전성을 개선하면서 C, C++ 및 Swift를 함께 사용하는 방법을 알아보세요. 안전하지 않은 C 및 C++ API가 Swift 코드 중 어느 곳에서 호출되는지 확인하는 방법, 더 안전하게 호출하는 방법 및 기본적으로 앱의 기존 C 및 C++ 코드를 더 안전하게 만드는 방법을 살펴봅니다.

    챕터

    • 0:00 - 서론
    • 2:39 - Swift에서 안전하지 않은 호출 찾기
    • 4:55 - 안전하게 C/C++ 호출하기
    • 7:25 - 포인터를 받는 함수
    • 17:13 - 포인터를 반환하는 함수
    • 20:16 - 맞춤형 유형 가져오기
    • 26:57 - C/C++ 안전성 개선하기
    • 30:48 - 요약

    리소스

    • -fbounds-safety: Enforcing bounds safety for C
    • Safely Mixing Swift and C++
      • HD 비디오
      • SD 비디오

    관련 비디오

    WWDC25

    • Swift로 메모리 사용량 및 성능 개선하기
  • 비디오 검색…

    안녕하세요 제 이름은 Yeoul입니다 저는 Apple의 Secure Language Extension 팀 매니저입니다 앱을 만들 때는 보안을 우선시하는 것이 중요합니다 잠재적인 공격자로부터 사용자의 개인정보를 보호해야 하죠 악의적인 행위자들은 종종 C 및 C++언어로 쓰인 코드의 취약점을 악용하죠 안전하지 않은 언어입니다 다행히도 앱에서 이미 Swift를 사용 중이라면 이 언어는 기본적으로 안전합니다 정말 좋은 소식이죠

    하지만 새 코드를 모두 Swift로 작성하더라도 앱에 아직 C 및 C++ 코드가 남아있을 수 있습니다 코드베이스의 오래된 부분에 말이죠 혹은 외부 라이브러리를 활용할 수도 있죠 이러한 언어를 혼합해서 사용할 때 Swift의 안전성이 훼손되지 않도록 하는 것이 중요합니다 높은 수준에서 문제는 원시 포인터를 인자로 받거나 반환하는 C 및 C++ 함수를 안전하게 호출하기가 매우 어렵다는 점이죠 잘못 호출하면 보안 및 안정성 버그가 발생할 수 있습니다 버퍼 오버플로나 use-after-free 같은 버그 말이죠

    그래서 C, C++ 포인터는 Swift에서 unsafe 유형으로 가져옵니다 예를 들어 C 정수 포인터는 UnsafeMutablePointer로 가져오죠 Swift는 유형 이름에 unsafe를 일부러 포함시켜 일반적인 안전성을 기대할 수 없다는 점을 알려줍니다 함수를 호출할 때 말이죠 Swift에서 안전하게 호출할 수 있는 함수가 있습니다 지금까지 C와 C++로는 그런 방법을 전달할 방법이 없었죠 이번 주제가 바로 이것입니다

    먼저 새로운 기능인 Strict Memory Safety에 대해 설명하겠습니다 Swift에서 안전하지 않은 호출을 식별하도록 돕죠

    두 번째로, C 및 C++ 함수에 부족한 정보를 주석으로 표시해 Swift가 더 안전하게 호출할 수 있도록 하는 방법을 다루겠습니다 맞춤형 C++ 유형을 안전하게 가져오는 주석 방법을 설명하고 끝으로, C 기반 언어를 Swift만큼 안전하게 만들 수는 없지만 C와 C++ 코드의 안전성을 높이는 몇 가지 도구를 소개하겠습니다

    Swift 6.2에 C, C++의 unsafe 함수 호출을 잡는 기능이 추가됐습니다 제가 직접 만든 앱을 예로 보여드리겠습니다 이 앱은 귀여운 반려동물 사진을 프로필로 공유할 수 있게 해줍니다 이 앱의 사용자의 매우 민감한 개인 정보에 접근할 수 있습니다 강아지 이름과 같이 말이죠 안전하게 보관할 정보요 이 앱은 Swift로 작성되었지만 맞춤형 이미지 필터 적용을 위해 C, C++ 코드도 호출합니다 앱 내 모든 C, C++ 호출 중 안전하지 않은 것들을 찾아 더 안전하게 바꾸려고 합니다 문제는 이런 호출을 찾기가 쉽지 않다는 점입니다 Swift는 기본적으로 안전하지만 C 및 C++과 상호 운용할 때 안전하지 않은 구문을 허용하기도 합니다 예를 들어, 내부적으로 %imageData는 UnsafeMutablePointer를 생성합니다 이름에서 알 수 있듯 안전하지 않은 유형이죠 하지만 코드를 읽을 때 이 점을 알아채기 쉽지 않습니다 안전하지 않은 함수에 대한 모든 호출을 발견하려면 이제 Swift 6.2에서 새로운 컴파일러 모드를 사용하면 됩니다 Strict Memory Safety라고 하죠 Strict Memory Safety가 켜져 있으면 컴파일러가 안전하지 않은 코드를 경고와 함께 모두 표시해 줍니다

    Strict Memory Safety는 기본적으로 비활성화되어 있지만 제 앱은 보안이 중요한 만큼 꼭 켜두겠습니다 Xcode에서 이를 확인해 보겠습니다

    프로젝트의 빌드 설정에서 Strict Memory Safety를 ‘Yes’로 설정하여

    사용하겠습니다 다시 빌드 시 컴파일러는 안전하지 않은 구문을 발견하는 데 도움이 되는

    이제 몇 가지 새로운 경고가 보이네요 안전하지 않은 코드의 대부분은 C와 C++ 포인터와 관련이 있습니다

    Swift의 포인터를 사용하는 함수를 안전히 호출하는 법을 보겠습니다

    먼저, C와 C++ 포인터를 안전하게 쓰기 어려운 이유를 살펴보죠

    포인터는 강력하고 유용한 도구입니다 메모리를 효과적으로 조사할 수 있는 능력을 주죠 복사할 필요 없이요 하지만 안전하게 사용하는 것은 매우 어렵죠 문제의 근원은 C와 C++가 프로그래머가 실수를 하지 않도록 돕지 않는 것에 있습니다 예를 들어, 실수로 메모리를 사용하는 것을 막을 장치가 없죠 버퍼의 경계를 넘어서 액세스하거나 해제된 후에는요

    하지만 좋은 소식이 있어요 Swift 6.2에는 안전한 포인터 유형인 Span이 도입됐습니다 이 유형은 포인터의 이점을 제공하면서도 그런 실수를 자동으로 방지해 주죠 Span은 포인터처럼 작동하지만 안전 기능이 내장되어 있습니다 Swift는 안전하지 못한 행동을 하지 못하도록 보장합니다 메모리를 수정해야 하는 경우 MutableSpan도 사용할 수 있습니다 Span에 대해 자세히 알아보려면 ‘Swift로 메모리 사용 및 성능 개선하기’를 시청하세요 Swift가 C 및 C++ 포인터를 unsafe 포인터 대신 Span으로 가져올 수 있다면 좋을 것입니다

    하지만 안타깝게도 컴파일러에는 두 가지 핵심 정보가 없습니다 이를 안전하게 수행하는 데 필요한 정보 말이죠 포인터의 경계에 관한 C++에 대한 정보 없이 Swift는 경계를 벗어난 접근을 막을 수 없습니다 그리고 포인터의 수명에 관한 C++ 정보 없이는 Swift는 해제 후 이것이 사용되는 것을 막을 수 없습니다 핵심 아이디어는 프로그래머가 누락된 정보를 제공합니다 Swift는 Swift Span 안전하지 않은 포인터를 처리할 수 있습니다

    Swift 6.2에서는 컴파일러에 누락된 정보를 제공할 수 있습니다 C와 C++ 코드에 주석을 추가하는 방식으로요 코드의 작동 방식을 변경하지 않는 방법이죠 단지 가정을 더 명확히 명시할 뿐입니다 이로써 Swift가 C, C++ 포인터 코드를 안전하게 호출합니다 이제 포인터를 받거나 반환하는 함수에 주석을 다는 법을 알아보죠 이를 위해 다시 제 앱으로 돌아가겠습니다

    첫 경고는 invertImage 함수의 사용에 관한 것입니다 원시 포인터를 사용하는 포인터죠

    그렇게 안전하게 호출하는 방법을 알려 드릴게요 포인터를 매개변수로 받는 함수 말이죠

    언급했듯 원시 포인터 경계 정보가 누락되었네요 그래서 경계 내에서 접근하는지 확인할 방법이 없습니다 주의해서 사용하지 않으면 범위를 벗어난 메모리 오류가 발생하죠 예를 들어 보겠습니다 InvertImage 함수는 Swift에서 호출됩니다 이 함수는 이미지 포인터를 받는데 이미지에 대한 원시 포인터입니다 그리고 크기는 별도의 매개변수로 전달됩니다

    하지만 이미지 포인터는 그냥 원시 포인터일 뿐입니다 실수로 사이즈를 통과하는 것을 막을 장치가 아무것도 없죠 너무 크니까요 이렇게 하면 함수가 버퍼의 경계를 넘어 읽고 쓰게 됩니다 그리고 이것이 바로 Span이 해결할 수 있는 문제죠 invertImage를 Span을 받는 Swift 함수로 가져온다고 가정하면 포인터와 크기를 따로 넘기는 대신 Span을 직접 전달할 수 있습니다 이렇게 하면 잘못된 크기를 넘기는 실수를 자동으로 방지할 수 있죠 Span은 항상 자신이 가리키는 메모리의 정확한 경계 정보를 가지고 있기 때문입니다 그런 다음, 컴파일러는 뒤에서 Span을 풀어주는 일을 맡게 됩니다 올바른 포인터와 크기를 추출하고 C 함수에 전달하죠 그렇게 하면 실수의 여지가 사라집니다

    컴파일러를 통해 가능한 일이지만 하지만 원시 포인터와 크기 연결이 없네요 invertImage 함수는 포인터가 imageSize 요소의 버퍼를 참조한다는 것을 가정합니다 하지만 암묵적인 가정일 뿐이죠 그 관계를 명시적으로 표현해야만 사람과 컴파일러 모두 이를 이해할 수 있습니다 counted_by 주석을 사용해 표현하면 됩니다 주석은 컴파일러에 포인터가 가리키는 메모리의 요소 개수를 알려줍니다 다음으로, 포인터에는 수명 정보가 없으니 noescape라는 주석도 필요합니다 하지만 지금은 무시해도 됩니다 이 내용은 나중에 살펴보죠 이 추가 정보를 제공하면 이제 Swift에서 Span을 바로 넘겨 함수의 안전한 호출이 가능합니다 그러면 컴파일러가 자동으로 나머지 작업을 처리합니다 실수가 발생할 가능성조차 사라집니다 invertImage 함수로 돌아가겠습니다

    invertImage 함수에 counted_By 및 noescape 주석을 추가하겠습니다

    그리고 장식으로 넘어가서 같은 주석을 추가하겠습니다

    그다음 Swift의 호출 사이트로 돌아가겠습니다

    이제 imageData를 직접 전달하여 Swift의 함수를 호출하겠습니다

    이제는 unsafe 포인터가 없으니 경고가 사라졌습니다

    다음 경고는 applyGrayScale 함수에 관한 것입니다 해당 함수는 안전하지 않은 C++ 유형을 사용한다고 합니다

    해당 함수의 C++ 정의를 살펴보겠습니다 이름에서 알 수 있듯 applyGrayScale은 입력 이미지에 회색조 효과를 적용하는 함수이고 이 함수는 이미지 뷰를 받는데 C++ Span 유형입니다 지금까지 우리는 Swift의 Span에 대해 이야기했습니다 하지만 C++에도 Span이라는 개념이 있습니다 Swift가 판단하기에 C++ Span이 안전하지 않다는 경고가 있습니다 같은 문제를 해결하려고 노력하고 있음에도 불구하고요 Swift의 Span과 마찬가지로 C++ Span도 표준 유형으로 다른 곳에서 소유한 연속된 메모리에 접근하는 데 사용됩니다 해당 메모리와 크기에 대한 포인터가 포함되어 있습니다 C++ Span은 크기를 알고 있기 때문에 Swift Span처럼 경계 초과 접근을 안전하게 검사할 수 있습니다 하지만 Swift Span과 달리 C++ Span은 수명 정보가 없어서 할당이 해제된 메모리의 접근을 차단하지 못합니다 그래서 C++ Span은 use-after-free 버그를 발생시키죠

    예를 들어 ApplyGrayScale 함수가 ImageView 이름의 C++ Span을 받는다고 하죠 Swift에 의해 생성된 배열을 가리키는 Span이죠

    내부에 Grayscale을 적용하면 포인터를 저장할 수 있습니다 cachedView와 같은 전역 변수에 있을 수도 있죠 그러면 다른 C++ 코드에서도 사용할 수 있게 됩니다

    하지만 이런 문제가 있습니다 함수가 반환되면 Swift는 배열의 할당을 해제할 수 있습니다 다른 것이 사용하지 않는다고 가정하면서요 이제 C++ 코드는 더 이상 유효하지 않은 메모리를 가리키는 포인터를 붙잡고 있습니다 이것이 바로 댕글링(dangling) 포인터입니다 이 포인터에 접근하면 전형적인 use-after-free 버그가 됩니다

    반면에 Swift Span은 안전합니다 Span은 가리키는 메모리보다 더 오래 살아남을 수 없습니다 함수가 Swift Span을 받으면 함수 내부에서만 쓸 수 있습니다 추후 사용을 위해 cachedView에 저장할 수 없습니다 함수가 종료된 후 포인터가 유지되는 경우의 동작은 탈출이라고 합니다 그리고 컴파일러가 이렇게 오류를 보고합니다 Span이 범위를 벗어나려고 할 때마다요

    그렇기에 Swift Span에서는 댕글링 포인터가 발생할 수 없습니다 use-after-free 버그도 설계 차원에서 방지됩니다

    C++ Span은 동일한 효과를 제공하지 않습니다 안전하게 사용하려면 함수가 imageView 매개변수를 보관하지 않도록 직접 검토해 댕글링 포인터를 피해야 합니다

    매개변수가 C++ 함수를 탈출하지 않는다는 것을 확인한 후 함수 정의에서 해당 정보를 다시 불러와야 합니다 그래야 사람과 컴파일러 모두 함수 동작을 이해할 수 있습니다 noescape 주석을 추가하면 그렇게 하는 데 도움이 되죠 noescape 주석은 원시 포인터와 참조에도 적용될 수 있습니다

    Swift 6.2부터는 noescape C++ Span 매개변수를 Swift Span으로 처리 가능합니다 Swift Span을 applyGrayscale에 이제 직접 전달할 수 있단 의미죠 이렇게 하면 매우 편리해 반복적인 unsafe 코드가 필요 없게 되죠 C++ 함수를 이렇게 쉽고 안전하게 호출할 수 있다니 정말 놀랍습니다

    적용된 Grayscale의 정의로 넘어가겠습니다

    그리고 매개변수 imageView에 noescape를 추가하겠습니다

    장식에도요

    이제 Swift의 핵심 사이트로 돌아가겠습니다

    이제 UnsafeMutableBufferPointer 임시 접근을 제거하고 imageData에서 가져온 Swift Span을 직접 전달할 수 있습니다

    applyGrayscale을 안전하게 호출해 이제 경고가 사라졌습니다

    다음 경고는 scanImageRow 사용에 관한 것입니다 이는 C++ Span을 받아서 또 다른 C++ Span을 반환합니다

    그렇다면 함수는 어떻게 안전히 호출할 수 있을까요? C++ Span과 같은 포인터를 반환하는 함수를 말이죠

    C++ Span을 반환하는 것은 위험할 수 있습니다 왜냐하면 가리키는 메모리가 유효한지 추적하지 않기 때문이죠 예를 들어 보겠습니다 ScanImageRow는 imageView를 C++ Span으로 사용합니다 선택된 imageData 행을 가리키는 또 다른 C++ Span을 반환합니다 함수가 반환되면 데이터가 할당 해제됩니다 하지만 반환 C++ Span은 아직도 그 메모리를 가리킵니다 댕글링 포인터죠 이 포인터에 액세스하면 use-after-free 버그가 발생합니다 반환값이 C++ Span이 아닌 Swift Span으로 다룰 수 있다면 버그는 발생하지 않았을 것입니다 컴파일러가 Swift Span을 반환하는 것조차 허락하지 않기 때문입니다 반환된 메모리가 여전히 유효하고 사용하기에 안전한지 알기 전에는 말이죠 그러면 반환된 Span을 안전하게 사용할 수 있는 경우는 언제일까요? Span은 imageView 매개변수가 가리키는 메모리 일부를 참조하죠 즉, imageView가 유효한 동안에만 함께 유효합니다

    이 관계를 lifetimebound라고 부릅니다

    이것이 Swift가 C++ Span을 Swift Span으로 가져오기 위해 필요로 하는 누락된 정보입니다 이 관계를 lifetimebound 주석으로 표현할 수 있습니다 이렇게 하면 컴파일러가 반환된 Span을 안전하게 사용할 수 있죠

    적용된 scanImageRow의 정의로 넘어가겠습니다

    lifetimebound 주석을 scanImageRow 함수의 imageView 매개변수에 추가합니다

    그리고 선언에도 같은 주석을 달겠습니다

    이제 lifetimebound 주석 덕분에 함수가 Swift Span을 받고 또 다른 Swift Span을 반환할 수 있습니다 이제 Swift 코드 쪽으로 이동하겠습니다

    여기서 이제 unsafe 포인터 접근을 제거하고 Swift Span을 바로 전달할 수 있습니다

    이제 함수가 unsafe 포인터 대신 Swift Span을 반환합니다 이를 다시 구축하면 경고가 사라질 것입니다

    안전에 대한 문제를 해결했으니 C 및 C++ 코드를 호출할 때 모든 경고가 사라진 것이 보입니다 지금까지 C, C++ 포인터를 Swift Span처럼 쓰는 법을 설명했습니다 하지만 C++에는 다른 관용적 유형도 있으며, 이들은 Swift로 직접 가져와 주석을 통해 안전하게 사용할 수 있습니다 이것은 맞춤화된 뷰 유형과 참조 카운트 유형입니다 먼저, 맞춤화된 C++ 뷰 유형을 안전하게 가져오는 법을 알아보죠 뷰 유형은 포인터나 참조를 담지만 메모리 소유권은 가지지 않는 구조체입니다 즉, Swift Span도 뷰 유형에 속합니다

    그렇다면 Swift Span이 왜 안전한지 자세히 살펴보겠습니다 내부적으로 Span은 특별한 Swift 유형으로 nonescapable 속성이 붙습니다 nonescapable 유형은 다른 유형의 메모리를 복사하지 않고 그 내용을 볼 수 있게 만드는 데 자주 쓰입니다 Span과 마찬가지로 Swift는 nonescapable 유형이 현재 맥락에서 벗어나지 않도록 합니다 그래야 가리키는 메모리보다 오래 살아남지 않게 되고 use-after-free 버그로부터 보호됩니다 C++ 뷰 유형도 non-escapable로 Swift에서 안전히 가져올 수 있죠 주석 하나를 추가하기만 하면 됩니다

    예를 들어 보겠습니다 제 앱에는 사용자 정의 C++ 구조체인 ImageView가 있습니다 이미지의 너비와 높이, 그리고 픽셀 데이터를 가리키는 포인터를 저장하는 구조체죠 imageView는 해당 픽셀 데이터를 소유하지 않고 다른 객체가 소유하며 더 이상 필요하지 않으면 해제하는 책임을 집니다

    그래서 imageView가 범위를 벗어나면 안전하지 않습니다 범위를 벗어난 경우 해당 뷰는 해제된 메모리를 사용할 수 있게 됩니다

    그래서 이 유형이 범위를 절대 벗어나지 않도록 하고 싶습니다 이를 위해 SWIFT_NONESCAPABLE 주석을 추가할 수 있습니다 컴파일러는 이렇게 C++ 유형을 nonescapable 유형으로 가져옵니다 기억해두면 좋은 규칙은 이렇습니다 자신이 소유하지 않은 뷰 또는 메모리에 대한 포인터 포함 구조체가 있는 경우 이 주석을 사용해야 합니다

    뷰 유형뿐만 아니라 C++ 및 기타 여러 언어에서도 매우 흔한 관용구입니다 유형이 참조하는 메모리를 소유하려면 참조 카운트를 통해 참조를 추적합니다 Swift는 이러한 유형도 주석으로 안전하게 가져올 수 있습니다 예로써, C++ ImageBuffer 구조체는 자신의 imageData를 소유합니다 구조체가 할당 해제되면 imageData도 할당 해제됩니다 Swift에서 참조 카운트 유형으로 이미지 버퍼를 가져오고 싶습니다 따라서 컴파일러는 자동으로 수명을 관리할 수 있습니다 이를 위해 SWIFT_SHARED_REFERENCE 주석을 달아서 어떤 함수를 호출해 참조 카운트를 증가 및 감소할지 지정합니다 이제 Swift는 이미지 버퍼를 참조 카운트 유형으로 간주합니다

    하지만 컴파일러에 필요한 정보는 더 있습니다 이미지 버퍼를 안전하게 반환하기 위해서는요

    C++ 함수가 이미지 버퍼를 반환할 때 가능한 상황은 두 가지가 있습니다 첫째, 함수가 새로 생성된 이미지를 반환하는 경우입니다 이때는 호출자가 사용을 마치면 이미지를 직접 해제해야 합니다 이 경우 SWIFT_RETURNS_RETAINED로 주석을 달겠습니다 이 주석은 Swift 컴파일러에 이미지가 더 이상 필요 없으면 호출자가 해제하도록 알려줍니다 두 번째는 기존 이미지를 참조로 반환하는 경우입니다 이때는 호출자가 참조를 유지해야 계속 사용할 수 있습니다 SWIFT_RETURNS_UNRETAINED로 주석을 달겠습니다 이 주석은 Swift 컴파일러에 이미지를 계속 사용하려면 참조를 유지하라고 알려줍니다 이러한 주석을 추가하면 소유권의 기대치가 명확해지고 Swift가 메모리를 안전하게 관리할 수 있습니다

    누락된 정보를 제공하기 위해 코드에 주석을 달면 Swift가 C 및 C++ 함수와 유형을 안전하게 사용할 수 있습니다 이러한 주석은 코드의 작동 방식을 변경하지 않습니다 그들은 단지 코드에서 명시적으로 가정을 할 뿐입니다

    지금까지 다룬 주석과 그 사용법을 다시 살펴보겠습니다

    함수 매개변수나 반환값이 포인터 또는 배열 유형이고 두 개 이상의 요소의 메모리를 가리킨다면 counted_by 주석을 사용하여 요소의 개수를 나타냅니다 매개변수가 다른 사람이 소유한 메모리를 참조하는 경우 매개변수가 함수를 탈출하지 않는 경우 noescape를 사용합니다

    함수 반환 값이 메모리를 참조하는 경우 수명이 매개변수의 수명에 따라 달라지는 경우 lifetimebound 주석을 추가합니다 이 정보를 추가하면 Swift가 활성화되어 포인터를 안전한 Swift Span 유형으로 가져올 수 있고 호출 코드에서 복잡한 작업 없이 바로 사용할 수 있습니다

    또 맞춤화된 C++ 유형에도 주석을 추가해 안전하게 관리할 수 있죠 C++ 유형이 소유하지 않은 뷰와 포인터, 메모리 참조를 저장하려면 SWIFT_NONESCAPABLE을 사용하세요 그러면 컴파일러가 non-escapable 유형으로 가져옵니다

    참조 카운트 유형이라면 SWIFT_SHARED_REFERENCE를 쓰세요 그러면 컴파일러가 자동으로 메모리를 관리합니다

    내용이 정말 많네요 지금 비디오를 일시 정지하고 간식을 먹거나 물을 마시고 싶다면 지금이 좋은 때입니다 꼭 다시 돌아올 거라고 약속만 해 주세요 다음에는 정말 흥미로운 새로운 도구를 보여드릴 거예요 C와 C++ 코드를 작업하는 것이 훨씬 더 안전해졌습니다

    좋아요, 방향을 바꿔 C와 C++ 코드에 대해 이야기하겠습니다 정말로 더 안전한 방법입니다 앱에서 Swift가 C와 C++를 안전하게 호출할 수 있도록 주석을 추가했습니다 이 상태의 C와 C++ 코드는 여전히 안전하지 않습니다 단 한 번의 실수만으로도 보안 버그가 발생할 수 있어요 완전한 안전을 위해 해당 코드를 Swift로 다시 쓰면 좋겠지만 실용적이지 않을 때도 있습니다 C와 C++를 Swift만큼 안전하게 만들 수는 없지만 일부 안전성을 더할 수 있는 도구들이 있습니다 먼저 C++ 경계 안전성 강화를 위해 개발한 도구를 소개하겠습니다

    C++의 경계 안전성이 높지 않은 이유가 궁금하실 수 있습니다 앞서 말했듯, 주어진 Span은 지금도 경계 정보를 저장하죠 그러나 문제는 이와 같은 Span에 대한 배열 서브스크립트의 범위가 C++에서는 기본적으로 검사되지 않는다는 점입니다 벡터 같은 표준 컨테이너도 마찬가지입니다 Xcode에는 C++ 표준 라이브러리 강화라는 기능이 있습니다 표준 C++ 뷰 및 컨테이너의 배열 서브스크립트에 경계 검사가 있는지 확인하죠 또한 표준 라이브러리에 몇 가지 다른 안전 검사도 추가합니다

    C++ 표준 라이브러리 강화 활성화 후에도 또 다른 문제가 있습니다

    경계 검사가 불가능한 경우 여전히 원시 포인터 사용이 가능한 점이죠 경계 정보가 없기 때문입니다 C++를 사용하는 가장 좋은 방법은 원시 포인터를 피하고 C++ Span과 같은 표준 유형을 사용하는 것입니다

    이를 돕기 위해 Xcode를 사용하면 C++에서 안전하지 않은 포인터를 사용할 때에 대비할 수 있습니다 이렇게 하면 코드를 확인하여 원시 포인터를 C++ Span 또는 필요에 따라 표준 컨테이너로 교체합니다

    이 오류는 경계 안전에 관한 것입니다 평생 안전하지 않죠

    C++ 프로젝트의 경계 안정성을 보장하려면 프로젝트 빌드 설정에서 Enforce Bounds-Safe Buffer in C++를 Yes로 설정하면 됩니다 C++ 표준 라이브러리 강화가 가능합니다 안전하지 않은 버퍼 사용 오류도 모두 가능하죠

    C는 어떨까요? C++와 달리 C에는 Span과 같은 경계 정보를 담을 표준 유형이 없습니다 C를 위해 새로운 언어 확장 프로그램을 개발했습니다 이는 경계 안전성을 보장하죠 이제 Xcode에서 사용할 수 있습니다 이 언어 확장 프로그램을 활성화하면 컴파일러가 누락된 경계 정보가 어디에 있는지 알려줄 것입니다 C 코드 전반에 걸쳐서요 누락된 정보를 표현하는 경계 주석을 추가할 수 있습니다 예를 들어, 이 버퍼에 counted_by 주석을 추가할 수 있고, 이 주석은 앞서 Swift와 C 상호 운용에서 썼던 것과 같은 것입니다

    그런 다음 컴파일러가 런타임 시 경계 검사를 삽입해 범위를 벗어난 메모리 액세스를 안전히 가둡니다

    Bounds Safety 확장 프로그램을 활성화할 수 있습니다 Xcode 프로젝트 설정에서 모든 C 파일에 대해 설정할 수 있죠 자세한 내용은 llvn.org 웹사이트에 업데이트된 Bounds Safety 문서를 확인하세요

    이 영상에서 Swift에서 안전을 보장하고 안전하게 C 및 C++ 코드를 호출하는 방법을 설명했습니다 C와 C++를 Swift만큼 안전하게 만들 수는 없습니다 하지만 기존보다 더 안전하게 만들 수는 있습니다

    최고의 안전을 위한 여러 팁을 알려 드리겠습니다 C, C++, Swift를 안전히 함께 사용할 수 있도록요

    Swift에서 Strict Memory Safety를 켭니다 이것은 안전하지 않은 구조를 사용할 때마다 경고할 것입니다 C와 C++ API의 안전하지 않은 용도를 찾는 데 도움이 됩니다 C와 C++ API에 주석을 추가해 Swift가 안전하게 상호 운용할 수 있도록 보장하세요

    기본적으로 C와 C++를 더 안전하게 만듭니다 C 및 C++용 새로운 경계 안전성 기능을 활성화하면 됩니다

    우리는 오픈 소스 커뮤니티와 협력하고 있습니다 C, C++, Swift가 원활하고 안전히 함께 작동하도록 만들기 위해서죠 여러분의 피드백과 참여는 저희에게 매우 중요합니다

    한번 시도해 보시고 어떻게 생각하시는지 알려 주세요 시청해 주셔서 감사합니다

    • 3:19 - Unsafety can be subtle

      // Swift
      var imageData = [UInt8](repeating: 0, count: imageDataSize)
      filterImage(&imageData, imageData.count)
    • 4:01 - Strict memory safety

      // Swift
      var imageData = [UInt8](repeating: 0, count: imageDataSize)
      filterImage(&imageData, imageData.count)
      //warning: Expression uses unsafe constructs but is not marked with 'unsafe'
    • 8:00 - Raw pointers don't prevent out-of-bounds errors

      // C/C++
      void invertImage(uint8_t *imagePtr, size_t imageSize);
    • 8:21 - Raw pointers don't prevent out-of-bounds errors

      // Swift
      var imageData = [UInt8](repeating: 0, count: imageSize)
      invertImage(&imageData, imageSize)
    • 8:30 - Raw pointers don't prevent out-of-bounds errors

      // Swift
      var imageData = [UInt8](repeating: 0, count: imageSize)
      invertImage(&imageData, 1000000000000)
    • 8:48 - Solution for out-of-bounds error

      // Swift
      func invertImage(_ imagePtr : inout MutableSpan<UInt8>)
    • 8:54 - Solution for out-of-bounds error

      // Swift
      var imageDataSpan = imageData.mutableSpan
      invertImage(&imageDataSpan)
    • 9:58 - Express bounds information using __counted_by

      // C/C++
      void invertImage(uint8_t *__counted_by(imageSize) imagePtr __noescape, size_t imageSize);
    • 12:10 - Unsafe function declaration taking a C++ span

      // C++
      using CxxSpanOfByte = std::span<uint8_t>;
      void applyGrayscale(CxxSpanOfByte imageView);
    • 13:21 - Unsafe C++ function caching a C++ span

      // C++
      CxxSpanOfByte cachedView;
      void applyGrayscale(CxxSpanOfByte imageView) {
        cachedView = imageView;
        // Apply effect on image ...
      }
    • 14:08 - Swift Span prevents escaping scope

      // Swift
      var cachedView: MutableSpan<UInt8>?
      func applyGrayscale(_ imageView: inout MutableSpan<UInt8>) {
        cachedView = imageView // error: lifetime dependent value escapes its scope
        // Apply effect on image ...
      }
    • 15:18 - Express lifetime information using __noescape

      // C++
      CxxSpanOfByte cachedView;
      void applyGrayscale(CxxSpanOfByte imageView __noescape) {
        // Apply effect on image ...
      }
    • 15:56 - Safely use a C++ Span as a Swift Span

      // Swift
      var imageDataSpan = &imageData.mutableSpan
      applyGrayscale(&imageDataSpan)
    • 17:21 - Returned C++ Span is unsafe

      // C++
      CxxSpanOfByte scanImageRow(CxxSpanOfByte imageView,
                                 size_t width, size_t rowIndex);
    • 18:06 - Swift Spans prevent use-after-free by design

      // Swift
      func scanImageRow(_ imageView : inout MutableSpan<UInt8>,
                        _ width : Int, _ rowIndex : Int) -> MutableSpan<UInt8>
      // error: a function with a ~Escapable result requires '@lifetime(...)'
    • 18:47 - Express lifetime dependency with __lifetimebound

      // C++
      CxxSpanOfByte scanImageRow(CxxSpanOfByte imageView __lifetimebound,
                                 size_t width, size_t rowIndex);
    • 18:50 - Safely return a C++ Span as a Swift Span

      // Swift
      var imageDataSpan = imageData.mutableSpan
      var rowView = scanImageRow(&imageDataSpan, width, y)
    • 22:29 - Import a C++ view type as SWIFT_NONESCAPABLE

      // C++
      struct ImageView {
        std::span<uint8_t> pixelBytes;
        int width;
        int height;
      } SWIFT_NONESCAPABLE;
    • 23:31 - Import a C++ reference-counted type

      // C++
      struct ImageBuffer {
        std::vector<uint8_t> data;
        int width;
        int height;
        std::atomic<unsigned> refCount;
      } SWIFT_SHARED_REFERENCE(retain_image_buffer, release_image_buffer);
      
      void retain_image_buffer(ImageBuffer *_Nonnull buf);
      void release_image_buffer(ImageBuffer *_Nonnull buf);
    • 23:57 - Safely return a reference-counted type

      // C++
      ImageBuffer *_Nonnull createImage() SWIFT_RETURNS_RETAINED;
      ImageBuffer *_Nonnull getCachedImage() SWIFT_RETURNS_UNRETAINED;
    • 27:51 - C++ standard library hardening

      // C++
      void fill_array_with_indices(std::span<uint8_t> buffer) {
        for (size_t i = 0; i < buffer.size(); ++i) {
          buffer[i] = i;
        }
      }
    • 28:59 - C++ unsafe buffer usage errors

      // C++
      void fill_array_with_indices(uint8_t *buffer, size_t count) {
        for (size_t i = 0; i < count; ++i) {
          buffer[i] = i; // error: unsafe buffer access
        }
      }
    • 30:11 - Bounds safety extension for C

      // C
      void fill_array_with_indices(uint8_t *__counted_by(count) buf, size_t count) {
        for (size_t i = 0; i < count; ++i) {
          buf[i] = i;
        }
      }
    • 0:00 - 서론
    • 특히 기본적으로 안전한 언어인 Swift와 취약할 수 있는 C 및 C++를 함께 사용할 때 앱 보안에 대해 알아보는 것이 좋습니다. Swift는 엄격한 메모리 안전성, C/C++ 함수 및 유형에 대한 주석, C/C++ 코드를 더 안전하게 만드는 도구 통합 시, 안전성을 강화하도록 새로운 기능을 도입했습니다.

    • 2:39 - Swift에서 안전하지 않은 호출 찾기
    • 이 예제는 이미지 필터를 위한 C 및 C++ 코드와 인터페이스하는 Swift로 작성된 앱을 보여주는데, 이는 보안 위험을 초래할 수 있습니다. Swift 6.2의 새로운 Strict Memory Safety 컴파일러 모드는 주로 C 및 C++ 포인터와 관련된 안전하지 않은 코드를 식별하고 표시하는 데 사용되어 앱의 보안을 강화할 수 있습니다.

    • 4:55 - 안전하게 C/C++ 호출하기
    • Swift 6.2에서는 ‘Span’이라는 새로운 안전 포인터 유형이 도입되었습니다. Swift는 C/C++ 코드에 경계 및 수명 정보를 주석으로 달아 안전하지 않은 포인터를 ‘Span’으로 변환하여 원래 C/C++ 코드를 수정하지 않고도 Swift에서 안전한 함수 호출을 지원합니다. ‘Swift로 메모리 사용량 및 성능 개선하기’에서 Span에 대해 자세히 알아보세요.

    • 7:25 - 포인터를 받는 함수
    • 함수가 원시 포인터를 받는 경우, 함수가 경계 밖에서 읽거나 쓰지 않도록 방지하는 경계 정보가 없습니다. 컴파일러가 원시 포인터와 해당 크기 간의 관계를 이해할 수 있도록 지원하려면 ‘counted_by’ 및 ‘noescape’ 주석이 필요합니다. 포인터에 이러한 주석이 달려 있으면 Span을 사용하여 Swift에서 함수를 호출할 수 있습니다. 이 예제에서는 Swift와 C++ ‘Span’ 유형 간의 차이점도 설명합니다. 두 방법 모두 연속된 메모리에 안전하게 액세스하는 것을 목표로 하지만, C++ ‘Span’은 수명 정보가 부족하여 사용 후 해제 버그가 발생할 수 있습니다. 이를 완화하기 위해 ‘noescape’ 주석을 사용하여 매개변수가 C++ ‘Span’이든 원시 포인터이든 상관없이 함수 범위 외부에 저장되거나 사용되어서는 안 된다는 것을 나타냅니다. 이러한 주석을 적용하고 ‘Span’을 사용하면 예제에서 안전하지 않은 포인터 사용이 없어지고, 메모리 관련 버그의 위험이 줄어들며, 코드의 유지 관리와 가독성이 향상되고, Swift 및 C/C++ 함수 간의 원활한 상호작용이 가능해집니다.

    • 17:13 - 포인터를 반환하는 함수
    • 해당 함수가 가리키는 메모리가 여전히 유효한지 추적하지 않기 때문에 포인터나 C++ Span을 반환하는 C/C++ 함수를 호출하는 것은 위험할 수 있습니다. 이를 완화하기 위해 ‘lifetimebound’ 주석을 통해 컴파일러가 안전한 사용을 강제함으로써 사용 후 해제 버그를 제거할 수 있습니다.

    • 20:16 - 맞춤형 유형 가져오기
    • Swift에서는 특정 C++ 관용적 유형을 주석과 함께 직접 가져와서 안전하게 사용할 수 있습니다. 메모리에 대한 포인터나 참조를 포함하는 구조체인 사용자 정의 뷰 유형은 ‘SWIFT_NONESCAPABLE’ 주석을 사용하여 탈출 불가한 유형으로 가져올 수 있습니다. 이렇게 하면 해당 메모리가 가리키는 것보다 더 오래 지속되지 않아 사용 후 해제 버그가 발생하는 것을 방지할 수 있습니다. 참조하는 메모리를 소유하고 카운팅을 통해 참조를 추적하는 참조 계산 유형은 ‘SWIFT_SHARED_REFERENCE’ 주석을 사용하여 가져올 수 있습니다. 이를 통해 Swift는 자동으로 수명을 관리할 수 있습니다. 또한, 함수 반환 값에 ‘SWIFT_RETURNS_RETAINED’ 또는 ‘SWIFT_RETURNS_UNRETAINED’로 주석을 달아 호출자가 반환된 참조 계산 객체를 해제할지 유지할지 지정하여 소유권에 대한 기대를 명시적으로 하고 Swift가 메모리를 안전하게 관리할 수 있습니다.

    • 26:57 - C/C++ 안전성 개선하기
    • C와 C++에는 경계 안전성을 강화하는 도구가 있습니다. Xcode의 C++ 표준 라이브러리 강화를 통해 표준 컨테이너와 뷰에 대한 경계 검사가 활성화되고 C++에서 안전하지 않은 포인터 사용에 대한 오류 기능이 켜질 수 있습니다. 경계 안전성을 위해 프로젝트의 빌드 설정에서 ‘C++에서 경계 안전 버퍼 사용 적용’을 ‘예’로 설정합니다. C의 경우, Xcode에서 사용할 수 있는 새로운 언어 확장 기능은 경계 안전성을 보장하여 경계 정보를 표현하기 위한 주석을 요구하고 런타임에 경계 검사를 삽입합니다. Xcode 프로젝트 설정에서 모든 C 파일에 대해 이 언어 확장을 활성화할 수 있습니다.

    • 30:48 - 요약
    • 이 세션의 정보를 사용하면 Swift, C, C++ 코드를 안전하게 함께 활용할 수 있습니다. 주요 단계로는 Swift에서 엄격한 메모리 안전성 활성화, 안전하지 않은 C/C++ API에 주석 추가, C/C++에서 새로운 경계 안전 기능 활용 등이 있습니다. 상호 운용성을 개선하기 위해 커뮤니티의 피드백과 참여를 장려하세요.

Developer Footer

  • 비디오
  • WWDC25
  • C, C++, Swift를 안전하게 함께 사용하기
  • 메뉴 열기 메뉴 닫기
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    메뉴 열기 메뉴 닫기
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    메뉴 열기 메뉴 닫기
    • 손쉬운 사용
    • 액세서리
    • 앱 확장 프로그램
    • App Store
    • 오디오 및 비디오(영문)
    • 증강 현실
    • 디자인
    • 배포
    • 교육
    • 서체(영문)
    • 게임
    • 건강 및 피트니스
    • 앱 내 구입
    • 현지화
    • 지도 및 위치
    • 머신 러닝 및 AI
    • 오픈 소스(영문)
    • 보안
    • Safari 및 웹(영문)
    메뉴 열기 메뉴 닫기
    • 문서(영문)
    • 튜토리얼
    • 다운로드(영문)
    • 포럼(영문)
    • 비디오
    메뉴 열기 메뉴 닫기
    • 지원 문서
    • 문의하기
    • 버그 보고
    • 시스템 상태(영문)
    메뉴 열기 메뉴 닫기
    • Apple Developer
    • App Store Connect
    • 인증서, 식별자 및 프로파일(영문)
    • 피드백 지원
    메뉴 열기 메뉴 닫기
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(영문)
    • News Partner Program(영문)
    • Video Partner Program(영문)
    • Security Bounty Program(영문)
    • Security Research Device Program(영문)
    메뉴 열기 메뉴 닫기
    • Apple과의 만남
    • Apple Developer Center
    • App Store 어워드(영문)
    • Apple 디자인 어워드
    • Apple Developer Academy(영문)
    • WWDC
    Apple Developer 앱 받기
    Copyright © 2025 Apple Inc. 모든 권리 보유.
    약관 개인정보 처리방침 계약 및 지침