영역 바이너리 옵션 타입
2016년 3월에 진행한 try! Swift Tokyo 행사의 강연입니다. 영상 녹화와 제작, 정리 글은 Realm에서 제공하며, 주최 측의 허가 하에 이곳에서 공유합니다.
Swift는 강력한 타입 시스템, value semantics, 자동 메모리 관리 등을 통해 안전성(safety)을 제공하면서도 뛰어난 성능을 보입니다. 또한, Swift는 메모리를 직접 할당하고 조작할 수 있는 도구도 제공하고 있습니다. 이 강연에서는 Swift에서 사용하는 포인터에 관해 다루고 있으며, typed 포인터, raw 포인터, buffer 포인터, 암묵적 브리징(bridging) 및 캐스팅(casting), 안전한 unsafe API 사용법에 대해 살펴볼 것입니다.
저는 오늘 Swift 표준 라이브러리에서 제공하는 unsafe 계열의 포인터 타입에 관해 이야기하고자 합니다. 강연에서는 Swift가 제공하는 여러 포인터 타입들의 동작 방식과 탄생 배경에 대해 살펴보고, 이들을 안전하게 사용하는 방법에 대해 알아보도록 하겠습니다.
Swift의 안정성(Safety)
unsafe 계열의 포인터 타입은 무엇이고, 무엇이 이들을 unsafe 하게 만들었는지에 대해 알아보기 전에, Swift의 안전성(safe)에 대해 우선 이야기해보겠습니다. Swift는 안전성을 최우선으로 하는 언어로 알려져 있는데 Swift에서 말하는 안전성이란 무엇일까요? Swift가 처음 소개되었을 때 Swift는 안전하다 라는 말은 정말 멋지게 들렸습니다. 앞으로는 크래쉬가 절대 발생하지 않는 프로그램을 짤 수 있게 되었으니 정말 엄청난 일이라고 생각했습니다.
Swift는 다양한 방법으로 안정성을 제공하는데 Optional 이 대표적인 예입니다. Optional 덕분에 null 포인터 이슈를 굉장히 쉽게 처리할 수 있게 되었지요.
ages 배열을 예로 들어볼까요? 이 배열에 관한 계산식을 작성하려 할 때, Swift의 안전성이 어떻게 동작하는지 살펴보겠습니다.
Optional 이 에러를 방지하는 역할을 하고 있네요. Array 타입의 first 속성은 Optional 타입이기 때문에 1 을 추가할 때 컴파일러가 미리 문제점을 파악해서 알려주고 있습니다.
어떻게 오류를 없앨 수 있을까요? 간단하게 언래핑 optional만 만들어주면 됩니다.
위의 코드에서 문제점을 발견할 수 있나요? 아무 문제가 없어 보이네요. 만약 빈 배열인 경우 어떤 일이 생길까요? 크래쉬가 발생합니다.
optional 바인딩이나 nil-coalescing 연산자를 이용해 안전하게 optional을 언래핑할 수 있는 여러 가지 방법이 있기 때문에 이것은 제 실수라고 할 수 있습니다. 크래쉬되지 않고도 값의 존재여부를 처리할 수 있으며, force unwrapping 연산자를 사용해 쉽게 크래쉬 상태를 만들 수도 있습니다. 왜 이러한 동작 방식이 Swift 언어의 한 부분이 된 것일까요?
이번에는 배열에 있는 값들의 평균을 계산하는 코드를 작성해볼까요? for 루프 대신 reduce 함수를 이용해 함수적인(functional) 방식으로 평균을 계산해보도록 하겠습니다.
optional에 대해 걱정할 필요 없는 깔끔하고 가독성 높은 Swift 코드네요.
이번에는 빈 배열을 0 으로 나눴을 때 어떻게 되는지 볼까요? 역시, Swift에서도 크래쉬가 발생하네요.
마지막으로 age 배열에서 마지막 요소에 접근하기 위해 ages[4] 를 사용했습니다. 하지만 배열 인덱스는 0 부터 시작한다는 사실을 깜빡했네요. 어떤 결과가 나올까요? “off by one” 에러가 발생했네요.
이곳에서 무슨 일이 일어난 걸까요? Swift의 규칙에 따라 문제가 전혀 없어 보이는 코드를 작성했는데 프로그램은 크래쉬되었네요.
이 언어를 안전하다고 말할 수 있을까요? 안전하다고 알려진 프로그래밍 언어가 이렇게 쉽게 크래쉬될 수 있는 거죠? Swift에서 이야기하는 안전성이 크래쉬로부터의 안전성이 아니라면 무엇으로부터 안전하다는 뜻일까요?
이런 개발 뉴스를 더 만나보세요
다시 좀 전의 예제로 돌아가 보죠. 네 번째 인덱스에 해당하는 요소가 없기 때문에 존재하지 않는 요소에 대해 접근을 시도한 예제 코드는 크래쉬되었습니다. Swift는 무엇으로부터 우리를 안전하게 지킨 걸까요? 크래쉬보다 더 나쁜 상황이 무엇일까요?
프로그램이 크래쉬되지 않고 계속 실행된다면 last 변수에 어떤 데이터가 있는지 누가 알까요?
시뮬레이터에서는 정상적으로 동작하더라도 실제 기기에서는 문제가 발생할 수도 있고 디버그 모드에서는 컴파일되었다가 릴리즈 모드에서는 컴파일이 실패할 수 있습니다. 또한, 문제가 바로 나타나지 않고 나중에 나타난다면 디버그하기 힘들고 문제 원인을 찾기도 어려워집니다.
예상을 벗어난 동작(Unexpected Behavior)
Swift는 예상을 벗어난 동작으로부터 우리를 지켜줍니다. Swift는 타입 시스템, value semantic, collection 및 numberic 타입의 바운더리 검사, 자동 메모리 관리 등을 통해 안전성을 제공합니다.
하지만, 성능이나 보다 세밀한 제어를 위해 Swift가 제공하는 안전성을 사용하지 않는 경우도 있는데 이때 unsafe 계열의 API가 사용됩니다.
Swift Unsafe APIs
Swift가 제공하는 안전 영역에서 벗어났다는 것을 명확히 알려주기 위해 Swift는 각각의 포인터 타입에 Unsafe 로 시작하는 이름을 붙였습니다. Swift의 unsafe API를 사용한다는 것은 Swift가 기본적으로 제공하는 안전성을 포기한다는 것을 의미하며, 개발자는 직접 안전성을 제공해야 합니다. 포인터를 이용하면 메모리에 직접 읽고 쓸 수 있게 됩니다.
Swift 언어의 메모리 레이아웃과 포인터
Swift의 메모리, 메모리 레이아웃, 포인터 동작 방식을 Swift 코드와 함께 설명하겠습니다.
우선, 메모리부터 살펴볼까요? Swift로 작성한 프로그램을 실행하면 우리가 작성한 타입과 함수들이 컴퓨터 메모리를 차지하게 됩니다. 변수나 상수를 만들 때마다 바이너리 형태로 저장된 값이 메모리에 존재하게 됩니다.
바이너리는 0과 1로 구성되어 있죠. 컴퓨터 메모리는 이들 바이너리로 가득 차 있습니다. 보통 바이너리는 아래와 같이 8개로 묶어 바이트 단위로 나타냅니다.
만약 이들을 바이너리 대신 16진수 표기법으로 표현한다면 아래와 같은 모습이 됩니다.
이들을 좀 더 압축된 형태로 표현하면 이런 모습이 됩니다.
각각의 행은 8 바이트(64비트)이며 요즘 모든 기기에서 사용하는 64비트 프로세서에서는 word 라는 이름의 단위로 표현할 수 있습니다.
메모리의 모든 위치에는 주소가 있기 때문에 이를 이용해 값을 저장하거나 불러올 수 있습니다. 아래 그림에서 주소는 왼쪽에 표기했습니다.
주소에도 16진수 표기법을 사용했습니다. 그림을 자세히 살펴보면, 각각의 행에 대한 주소는 다음 행과 8 만큼 차이가 난다는 사실을 알 수 있습니다. 각각의 행이 8 바이트라고 했던 것을 기억하시나요? 이 때문에 메모리 주소도 바이트를 기준으로 표기했습니다. 참고로 메모리 주소와 관련해서는 0과 1이 아닌 바이트가 가장 작은 단위입니다.
프로그램에서 작성한 값은 항상 메모리에 저장됩니다.
위의 예제코드에서 age 는 Int 이며, Int 는 word 크기의 signed integer입니다. 64비트 기기라면, 이 값은 word의 64 비트 모두를 사용해 저장됩니다. 위의 그림에서 age 변수는 메모리에서 한 행 전체를 사용한 것을 알 수 있습니다. Swift의 withUnsafePointer 함수를 사용하면 값 대신 값에 대한 포인터에 일시적으로 접근할 수 있게 됩니다.
예제에서는 withUnsafePointer(to:_:) 에 &age 를 인자로 전달했고, 함수 내부에서 실행되는 trailing closure에서 포인터 인자인 agePointer 를 얻을 수 있습니다. 이 포인터는 age 변수의 값이 아닌 age 변수의 주소를 나타냅니다.
agePointer 는 age 변수의 주소를 값으로 가지고 있습니다. agePointer 는 값이 아니라 메모리에서의 위치를 나타냅니다. 만약 포인터가 가리키고 있는 값에 접근하고 싶을 때는 포인터의 pointee 속성을 사용하면 됩니다.
정리하자면, 포인터란 메모리에서 값의 위치에 접근하거나 변경하는 방법입니다.
Unsafe 포인터 타입
Swift 표준 라이브러리에는 8가지 unsafe 포인터 타입이 있습니다. 이것은 다시 4개의 포인터와 4개의 buffer 포인터로 나눌 수 있습니다.
우선 4개의 포인터 타입에 대해 살펴보죠. 이들은 UnsafePointer , UnsafeRawPointer , UnsafeMutablePointer , UnsafeMutableRawPointer 이며 공통으로 메모리 주소에 대한 래퍼(wrapper) 기능을 제공하며 내부에서는 unsigned integer로 표현됩니다. 왜 4종류의 다른 타입이 존재할까요?
이들은 바운더리 검사, 타입 안전성, 메모리 관리 등을 하지 않는 unsafe API지만, Swift는 여전히 당신을 도울 준비가 되어있습니다. 그래서 타입 시스템을 적용한 unsafe API가 있는 것이고, 이러한 관점에서 다시 4개의 타입을 두 개의 그룹으로 나눌 수 있습니다.
Typed 포인터와 Raw 포인터
unsafe API는 “typed 포인터”와 “raw 포인터” 그룹으로 나눌 수 있습니다. UnsafePointer , UnsafeMutablePointer 가 typed 포인터에 해당합니다. 이들 포인터가 가리키는 메모리 주소에는 특정 타입의 값이 저장되어 있습니다. 예를 들면, Int 타입이 저장된 값에 대한 포인터가 있다면, 포인터의 pointee 에 접근했을 때 Int 타입의 값을 얻게 됩니다. Swift는 같은 메모리에 서로 다른 타입들이 함께 접근하는 것을 제한하고 있습니다. 따라서, signed integer와 다른 타입이 같은 메모리 주소를 사용하려고 시도할 때 어떻게 동작해야 하는지에 대해서는 정의되어 있지 않습니다(undefined behavior). typed 포인터는 메모리를 일시적 또는 영구적으로 다른 타입과 연결해주는 메서드를 제공하여 이 문제를 해결합니다.
typed 포인터는 그것이 가리키고 있는 타입의 크기와 alignment에 대한 정보를 알고 있기 때문에 typed 포인터를 사용할 때는 stride나 alignment에 대해 고민하지 않아도 됩니다. 앞선 예제에서 age 변수에 대한 포인터로 UnsafePointer 가 사용되었는데 typed 포인터이기 때문에 age 변숫값에 대한 위치(location)와 타입 정보를 알고 있습니다.
또한, 연속된 요소들로 구성된 배열에 접근하는 경우 typed 포인터는 요소 하나에 하나의 인스턴스를 매칭시키기 때문에 실수로 인스턴스의 중간 부분에 매칭되는 경우는 발생하지 않습니다.
반면 raw 포인터의 경우 저장된 값이 어떤 타입인지에 대한 정보가 없습니다.
raw 바이트를 사용하거나 메모리에서 로드되길 원하는 Swift가 제공하는 여러 포인터 타입들과 동작 방식 데이터 타입을 직접 명시하는 방법을 이용해 raw 포인터가 가리키는 데이터에 접근할 수 있습니다. age 에 대해 typed 포인터 대신 raw 포인터를 사용한다면 이전 주소와 같은 주소를 참조하였더라도 해당 주소에 저장된 값에 대한 타입 정보는 알 수 없습니다. 즉, 해당 주소에 64비트 signed integer가 저장되어 있는지 알지 못합니다. raw 포인터는 주소 그 자체입니다. 바이트를 이용해 해당 주소에 접근할 수도 있고 저장된 값의 타입에 상관없이 그 값을 불러올 수 있으며, 메모리 바인딩을 통해 raw 포인터를 typed 포인터로 변환할 수도 있습니다.
Mutable 포인터와 Immutable 포인터
unsafe API를 mutability를 기준으로 분류할 수도 있습니다. 포인터는 어떤 값을 참조하기 위한 것이므로 포인터를 let 으로 선언하더라도 포인터가 참조하는 Swift가 제공하는 여러 포인터 타입들과 동작 방식 메모리의 값을 변경할 수 있습니다. Swift는 인스턴스 레벨에서 mutability를 제어하는 대신, 타입 시스템 레벨에서 mutable 포인터에 의해 참조되는 값을 변경할 수 있는 기능을 제공하여 mutability를 처리하고 있습니다.
참조하는 메모리에 대해 읽기만 가능한 immutable typed 포인터, raw 포인터와 함께 이들에 대한 mutable 버전도 있는 것이죠. Swift는 mutable 포인터에 대한 초기화, 비초기화(de-initialization), mutable 포인터 할당 등의 기능을 제공하고 있습니다.
buffer 포인터
이제 buffer 포인터에 대해 알아볼까요? buffer 포인터는 항상 count가 따라다닙니다. 특정한 하나의 주소에 저장하는 대신, buffer 포인터는 메모리의 범위를 지정합니다. buffer 포인터는 typed, raw, mutable, immutable 포인터 형태가 있습니다.
buffer 포인터는 collection처럼 동작할 수 있습니다. 특정 메모리 영역에 저장된 내용을 이터레이트(iterate)할 수 있으며, Array에서 사용했던 대부분의 연산자를 사용할 수 있습니다.
예제 코드에서는 withUnsafeBytes 함수에 age 를 전달했으며, 이 함수는 인자로 넘겨준 age 변수의 바이트를 참조하는 UnsafeRawBuffer 포인터 타입의 closure를 내부에서 호출합니다.
buffer를 일반적인 collection처럼 사용할 수 있습니다. count도 이용했고, first 속성을 이용해 첫 번째 값에 접근했으며, subscript도 사용했습니다.
이들이 메모리에 저장된 값의 raw 바이트라는 사실을 잊지 말아야 합니다. Int 가 8 바이트 크기이기 때문에 8이라는 count 값을 얻게 된 것이고, age 값의 첫 번째 바이트를 읽었을 때 5 라는 결과가 나온 것입니다.
만약 age 의 값이 1바이트만으로 표현할 수 없을 때는 어떤 결과가 나올까요? 2,000살인 간달프의 나이를 다룬다고 가정했을 때 age 변수의 메모리에 대한 첫 번째 byte는 16진수 값으로는 d0 이고 정수형으로는 208이 될 것입니다. 다시 한번 강조하지만, 우리는 raw 포인터를 사용하고 있기 때문에 인스턴스 레벨이 아니라 바이트 레벨에서 이 값을 읽어야 합니다.
C API 임포트
unsafe 포인터 타입이 필요한 때는 언제일까요? 대부분의 개발자에게 이들이 필요할 때는 크게 두 가지 경우일 것입니다. unsafe 포인터 타입은 typed 포인터나 Void 포인터가 사용된 대부분의 C API와의 호환성을 제공합니다. 따라서 C API와 호환이 필요한 경우 unsafe API를 사용할 수 있습니다. 다른 하나는 unsafe 포인터 타입으로만 최적화 작업이 가능한 경우입니다. 예제를 통해 좀 더 자세히 살펴보도록 하죠.
예제코드는 macOS 10 SearchKit 프레임워크가 제공하는 SKSearchFindMatches 함수 시그니처를 간략하게 보여주고 있습니다. 약간 복잡해 보이지만 in, out 인자를 가진 일반적인 C 함수 형태라고 보시면 됩니다. 세 가지 인자가 있는데 한 개는 함수에 대한 input이며 나머지 두 개는 output입니다.
이 함수는 검색이 시작되면 반복적으로 호출됩니다. 매번 호출될 때마다 out 인자값에는 검색결과가 저장되며 검색이 완료되면 false 를 반환합니다. inMaximumCount 는 최대 검색결과 수를 의미하며, 첫 번째 out 인자값인 C 배열에 저장되는 documentID의 최대 개수를 설정하는 값이기도 합니다. 두 번째 out 인자는 반환되는 실제 검색결과 수를 의미합니다.
두 개의 out 인자들은 모두 typed mutable 포인터 타입이지만 pointee 타입은 다르네요. 흥미로운 사실은 이 함수를 호출할 때 unsafe 포인터를 직접 다룰 필요가 없다는 점입니다.
Swift는 &documentIDs 와 같은 형태로 inout 문법을 사용하면 변수 또는 배열을 typed 포인터나 raw 포인터로 암묵적(implicit)으로 변환할 수 있습니다.
documentIDs 배열과 foundCount 변수를 생성하고, inout 문법을 통해 SKSearchFindMatches 함수에 대해 인자로 전달했습니다. &documentIDs 는 documentIDs 배열의 요소들을 가리키는 포인터로 변환하는 것이고, &foundCount 는 foundCount Swift가 제공하는 여러 포인터 타입들과 동작 방식 변수에 대한 포인터로 변환하는 것입니다.
하지만, 암묵적 변환에는 제약사항이 있다는 것을 잊지 말아야 합니다. 암묵적 포인터 변환을 사용해 documentIDs 배열을 인자로 전달하면, 배열의 첫 번째 요소에 해당하는 포인터만 전달되는 것입니다. 이 포인터를 전달받은 함수는 배열의 크기에 대한 정보를 알지 못하고, 배열의 count 도 변경할 수 있는 능력도 없습니다. 때문에 inMaximumCount 만큼의 요소들을 저장할 수 있는 충분한 공간을 가지고 있는 배열을 넘겨주는 것이 중요합니다.
또한, 암묵적인 변환으로 생성된 포인터는 함수가 호출된 시점에만 유효(valid)합니다. 따라서, 함수 실행 이후에 이들 포인터를 이용하는 것은 예상치 못한 결과를 가져올 수 있습니다.
documentIDs 를 얻고 난 후 배열에 대한 루프를 수행할 수 있으며 각각의 검색결과를 처리할 수 있는 핸들러를 호출할 수 있습니다.
예제 코드는 예상한 대로 잘 동작하고, 테스트도 무난히 통과할 테지만, 성능 테스트를 해보면 unsafe 포인터를 명시적으로 사용하여 이 코드 성능을 좀 더 향상할 수 있다는 것을 깨닫게 될 것입니다.
약간의 속도 향상을 위해 Swift 언어가 제공하는 안전성을 포기해야 하는 상황에 직면했네요. 이러한 최적화가 정말로 필요하고 도움이 된다는 확신이 들 때만 최적화를 진행하길 바랍니다.
Swift가 제공하는 안전성을 개발자가 직접 제공하기 위해서 예제 코드를 약간 수정해야 합니다. 우선, documentIDs 를 선언한 부분을 살펴보죠. 배열 초기화는 Array(repeating:count:) 를 이용해 documentIDs 를 위한 100개의 공간을 확보하고, 각각의 공간을 0으로 초기화했습니다.
일반적으로는 이러한 초기화 Swift가 제공하는 여러 포인터 타입들과 동작 방식 방법은 괜찮다고 할 수 있습니다. Array 타입은 초기화가 완료되기 전까지는 요소에 접근할 수 없게 함으로써 안전성을 제공합니다. 하지만 우리 예제의 경우 배열 요소들에 접근하는 코드가 나타나기 전에 SKSearchFindMatches 함수에 배열이 전달되는 코드가 먼저 발생하며, 이 함수에서는 배열에 검색결과를 저장하는 작업을 수행할 것입니다. 때문에, 초기화 과정이 꼭 필요하지는 않습니다.
다른 접근법을 고려해보죠. documentIDs 를 빈 배열로 선언한 다음, 충분한 공간을 확보하면 어떨까요?
괜찮은 방법처럼 보이지만, 아쉽게도 이 방법은 좋은 해결책이 아닙니다. 함수에 documentIDs 를 전달했을 때 한 개의 포인터만 얻을 수 있었던 점을 기억하시나요? 배열에 대한 count 정보를 알 수도 없고 count를 변경할 수도 없습니다. 그 때문에 이 방법으로는 배열에 들어있는 요소들에 접근할 방법이 없습니다.
추가적인 작업을 할 곳이 한 군데 더 있는데 documentIDs 의 요소 i 에 접근할 때입니다.
배열의 subscript를 사용할 때마다 배열은 바운더리를 벗어나는 요소에 접근하는 시도를 막기 위해 바운드 검사를 수행합니다.
이것은 굉장히 중요한 안전성 기능이지만 성능 향상을 위해 취할 수 있는 모든 방안을 고민해봐야 합니다. 이터레이팅을 수행하는 동안 우리가 생성한 바운더리 안에서 머물게 한다면, 굳이 요소에 매번 접근할 때마다 바운드 검사를 수행할 필요는 없습니다.
앞서 언급한 두 가지 경우에 대한 해결책은 documentIDs 를 배열 대신 typed mutable 포인터로 선언하는 것입니다. allocate 함수를 호출하면 요소들 크기에 해당하는 공간을 확보하고 블록에 대한 시작 부분을 가리키는 포인터가 반환됩니다.
메모리를 할당했으니 메모리 해제도 해야겠죠? 배열에서 unsafe 포인터로 변경했기 때문에 메모리 해제 작업이 필요합니다. Swift에서는 메모리 할당 코드 바로 뒤에 defer 블록을 만들고 그 안에 메모리 해제 코드를 추가하면 안전하게 메모리를 해제할 수 있습니다.
예제 코드에서 추가로 수정되어야 하는 부분은 인자로 전달될 때 사용되었던 & 를 생략하는 것입니다. 암묵적 변환 대신 포인터를 직접 넘겨주기 때문에 inout 문법을 사용하지 않아도 되는 것이죠.
배열에서 subscript를 사용했던 것처럼 포인터에 대해서도 subscript를 사용할 수 있습니다. 그 때문에 나머지 코드들은 수정하지 않아도 됩니다. 지금까지 최적화 작업을 진행해봤습니다. 불필요한 초기화 코드와 바운드 검사를 제거했고 Swift가 기본적으로 제공했던 아래의 세 가지 기능을 직접 처리했습니다.
- documentIDs에 대한 읽기 요청이 발생하기 전에 documentIDs를 초기화해야 한다.
- 할당한 메모리는 반드시 해제해야 한다.
- 할당한 공간의 바운더리를 벗어나면 안 된다.
이제 마지막 예제네요. 제가 가장 좋아하는 정렬 알고리즘인 버블 정렬입니다.
bubbleSort 함수는 Comparable 프로토콜을 준수하는 배열을 인자로 받아, 이 배열을 오름차순으로 정렬합니다. 버블 정렬 대신 다른 정렬 방법을 사용하여 성능을 향상하고 싶겠지만 여기서는 정렬 방법을 변경할 수 없다고 가정하겠습니다.
Swift의 Array 타입은 항상 바운드 검사를 수행하는 Array 대신 unsafe buffer 포인터를 사용할 수 있는 메서드를 제공합니다. unsafe buffer 포인터를 사용하게 되면 디버그 모드만 바운드 검사가 수행되고 릴리즈 모드에서는 검사가 생략됩니다.
배열에서 withUnsafeMutableBufferPointer 메서드를 호출하도록 예제 코드를 수정했습니다. 메서드만 변경했을 뿐인데 성능은 눈에 띄게 향상되었습니다.
unsafe 포인터 사용을 통해 얻은 이점은 분명합니다. 요소들을 버퍼에 유지함으로써 배열과 유사한 인터페이스를 제공하면서도 바운드 검사를 생략하여 성능은 향상했습니다.
지금까지 unsafe 포인터 타입에 대해 알아보고, unsafe 포인터 타입으로 안전한 코드를 작성하는 방법에 대해 살펴보았습니다. 이번에는 잘못된 포인터 사용법에 대해 알아보겠습니다.
잘못된 포인터 사용법
unsafe 포인터 타입을 사용할 때 가장 많이 실수하는 부분은 암묵적 포인터 변환 또는 withUnsafePointer , withUnsafeByte 함수를 통해 얻은 포인터를 escape 하는 것입니다. 예제를 통해서 이 문제를 좀 더 살펴보죠.
age 변수를 선언하고 UnsafeMutablePointer 를 이용해 변수에 대한 포인터를 생성했습니다. 그리고 포인터 주소에 저장된 값을 변경했습니다. age 변수에 의해 사용된 같은 주소를 쓰고 있기 때문에 값이 변경됩니다.
언뜻 보기에는 괜찮아 보이지만 이 코드에는 매우 큰 문제가 있습니다. 암묵적 변환 대신 명시적인 형태로 코드를 약간 수정해보겠습니다.
암묵적 포인터 변환을 이용해 UnsafeMutablePointer 를 초기화하는 것은 withUnsafeMutablePointer closure에서 escaping 포인터를 전달받은 것과 같습니다. 문제없이 컴파일되지만 사실 이것은 예상치 못한 동작(undefined behavior)에 해당합니다.
이러한 형태의 코드는 오류를 발견하기가 쉽지 않습니다. 명시적으로 escape를 선언하지도 않았고 함수에 변수를 넘겨줄 때 & 를 사용하는 것은 일반적인 형태니까요. 따라서 두 가지 규칙을 꼭 기억하시길 바랍니다. 첫째, withUnsafe- 에서 얻은 포인터는 절대 escape 하지 마세요. 둘째, 암묵적 변환으로 변수에 대한 포인터를 얻지 마세요.
감사합니다. 이 강연 내용이 Swift unsafe 포인터 타입을 이해하는 데 도움이 되었으면 좋겠네요.
Q: bindMemory 메서드와 assumingMemoryBound 메서드 차이점은?
A: unsafe raw 포인터는 그들이 가리키고 있는 메모리에 대한 정보가 없지만 메모리 자체는 바운드된 상태일 수 있습니다. 그 때문에 integer 타입과 바운드된 메모리에 대한 raw 포인터를 같은 메모리지만 다른 타입과 바운드된 raw 포인터로 만들 수 있습니다. 인스턴스에 좀 더 쉽게 접근하기 위해 raw 포인터를 type 포인터로 변환할 때 메모리를 바인드해야 합니다. 즉 이 타입으로 메모리를 바인드할 것이라고 컴파일러에 알려주는 것이죠. bind 는 unsfae typed pointer를 반환합니다.
assumingMemoryBound 를 호출하면 검사를 우회하는 것입니다. 컴파일러 측면에서 보면 실제로는 어떠한 바인드도 수행하지 않습니다. 대신 이것은 이미 바운드되어 있다고 가정하기 때문에 여러분은 메모리가 해당 타입과 이미 바운드되어 있다는 정보를 가지고 있습니다. 만약 아직 메모리가 바운드되지 않은 상태에서 raw 메모리를 할당하고 assumingMemoryBound(to:) 를 호출했다면 이것은 안전하지 않고 예상치 못한 동작에 해당하게 됩니다. 왜냐하면, 해당 타입과 실제로 바운드되지 않은 상태에서 메모리에 접근했기 때문이죠.
Q: 마지막에 보여주신 예제에 대해 궁금한 점이 있습니다. 예제를 설명하시면서 포인터에 대한 escape를 하지 말라고 하셨는데 escaping 속성을 추가하더라도 절대 escape를 하지 말라는 뜻인가요?
A: 네 escaping 속성을 추가한 경우라도 포인터를 가지고 escape하지 말라는 뜻입니다. 암묵적 포인터 변환을 위해, 변수를 인자로 전달해서 mutable 포인터로 변환하고 싶을 것입니다. 예제 코드의 UnsafePointer 초기화 코드처럼 말이죠. C 함수에 변수를 전달할 수도 있지만, C 함수는 일반적으로 반환하지 않기 때문에 C 함수에서 반환받을 수 있다는 보장이 없습니다.
문제는 이것이 대부분의 경우 잘 동작한다는 것이죠. 실제로 마지막 예제는 playground에서도 잘 동작합니다. age 변숫값을 변경할 수 있죠. 하지만 computed 속성인 경우라면 의도대로 동작하지 않을 것입니다. 컴파일러는 포인터와 관련된 최적화를 수행하지 않게 설정할 수 있는데 만약 최적화 옵션을 적용해서 실행할 경우 해당 코드 라인을 삭제 처리하고 age 변숫값은 업데이트되지 않을 것입니다.
2016년 3월에 진행한 try! Swift Tokyo 행사의 강연입니다. 영상 녹화와 제작, 정리 글은 Realm에서 제공하며, 주최 측의 허가 하에 이곳에서 공유합니다.
bwboundaries
B = bwboundaries( BW ) 는 이진 영상 BW 에 있는 객체의 외부 경계선뿐 아니라 이러한 객체 내에 있는 구멍의 경계선도 추적합니다. bwboundaries 는 가장 바깥쪽(부모) 객체가 포함하는 자식(부모 객체에 완전히 둘러싸인 객체) 객체의 경계선도 추적합니다. 경계선 픽셀 위치로 구성된 셀형 배열인 B 를 반환합니다.
B = bwboundaries( BW , conn ) 은 객체의 외부 경계선을 추적합니다. 여기서 conn 은 부모 객체의 경계선과 자식 객체의 경계선을 그릴 때 사용할 연결성을 지정합니다.
B = bwboundaries( BW , conn , options ) 는 객체의 외부 경계선을 추적합니다. 여기서 options 는 'holes' 또는 'noholes' 이며, 다른 객체 내에 있는 구멍의 경계선을 같이 추적할지 여부를 지정합니다.
[ B , L ]= bwboundaries( ___ ) 는 객체와 구멍에 대해 지정된 레이블의 행렬 L 을 반환합니다.
[ B , L , n , A ] = bwboundaries( ___ ) 는 찾은 객체의 수 n 과 인접 행렬 A 를 반환합니다.
영상에 영역 경계선 겹치기
회색조 영상을 작업 공간으로 읽어 들입니다.
국소 가변 이진화를 사용하여 회색조 영상을 이진 영상으로 변환합니다.
영상에서 영역의 경계선을 계산한 후 영상에 경계선들을 겹칩니다.
영상에 영역 경계선을 겹친 후 영역 번호 달기
이진 영상을 작업 공간으로 읽어 들입니다.
영상에서 영역의 경계선을 계산합니다.
경계선이 겹친 채로 영상을 표시합니다. 레이블 행렬을 기반으로 하여, 모든 경계선 옆에 영역 번호를 추가합니다. 개별 레이블을 읽으려면 축소 툴을 사용하십시오.
spy 함수를 사용하여 인접 행렬을 표시합니다.
객체 경계선은 빨간색으로, 구멍 경계선은 녹색으로 표시하기
이진 영상을 작업 공간으로 읽어 들입니다.
객체 경계선은 빨간색으로 표시하고 구멍 경계선은 녹색으로 표시합니다.
부모 객체의 경계선은 빨간색으로, 구멍은 녹색으로 표시하기
영상을 작업 공간으로 읽어 들입니다.
부모 객체의 경계선은 빨간색으로 표시하고 이에 속하는 구멍은 녹색으로 표시합니다.
입력 인수
BW — 입력 이진 영상
2차원 숫자형 행렬 | 2차원 논리형 행렬
이진 입력 영상으로, 2차원 논리형 또는 숫자형 행렬로 지정됩니다. BW 는 0이 아닌 픽셀은 객체에 속하고 0인 픽셀은 배경이 되는 이진 영상이어야 합니다. 다음 그림은 이러한 구성요소를 보여줍니다.
데이터형: single | double | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 | logical
conn — 픽셀 연결성
8 (디폴트 값) | 4
픽셀 연결성으로, 다음 표에 있는 값 중 하나로 지정됩니다.
2차원 연결성
경계가 서로 닿으면 픽셀이 연결됩니다. 두 개의 인접한 픽셀이 모두 켜져 있고 가로 또는 세로 방향으로 연결되어 있으면 두 픽셀은 동일한 객체에 속합니다.
경계 또는 코너가 서로 닿으면 픽셀이 연결됩니다. 두 개의 인접한 픽셀이 모두 켜져 있고 가로, 세로 또는 대각선 방향으로 연결되어 있으면 두 픽셀은 동일한 객체에 속합니다.
데이터형: double
options — 부모 객체의 경계선과 자식 객체의 경계선을 둘 다 탐색할지 여부 지정
'holes' (디폴트 값) | 'noholes'
부모 객체의 경계선과 자식 객체의 경계선을 둘 다 탐색할지 여부를 지정하는 옵션으로, 다음 중 하나로 지정됩니다.
객체 경계선과 구멍 경계선을 둘 다 탐색합니다. 이는 디폴트 값입니다.
객체(부모 및 자식 객체)의 경계선만 탐색합니다. 이 경우 성능이 향상될 수 있습니다.
데이터형: char | string
출력 인수
B — 경계선 픽셀의 행과 열 좌표
p×1 셀형 배열
경계선 픽셀의 행과 열 좌표로, p×1 셀형 배열로 반환됩니다. 여기서 p는 객체와 구멍의 개수입니다. 셀형 배열의 각 셀에는 q×2 행렬이 있습니다. 행렬의 각 행에는 경계선 픽셀의 행과 열 좌표가 들어 있습니다. q는 해당하는 영역의 경계선 픽셀 수입니다.
L — 레이블 행렬
음이 아닌 정수로 구성된 2차원 행렬
인접 영역의 레이블 행렬로, 음이 아닌 정수로 구성된 2차원 행렬로 반환됩니다. k번째 영역은 L 에서 값이 k인 모든 요소를 포함합니다. L 로 나타낸 객체와 구멍의 개수는 max(L(:)) 과 같습니다. L 에서 값이 0인 요소들은 배경을 구성합니다.
데이터형: double
n — 찾은 객체의 개수
음이 아닌 정수
찾은 객체의 개수로, 음이 아닌 정수로 반환됩니다.
데이터형: double
A — 경계선과 구멍 간 부모-자식 객체 종속성
희소 형식의 논리형 정사각 행렬
경계선과 구멍 간의 부모-자식 객체 종속성으로, 변 길이가 max(L(:)) 인 double 형 클래스의 희소 형식의 논리형 정사각 행렬로 반환됩니다. A 의 행과 열은 B 에 저장된 경계선의 위치에 해당합니다. B 에서 처음 n 개의 셀은 객체의 경계선입니다. A(i,j)=1 은 객체 i 가 객체 j 의 자식 객체임을 의미합니다. 다음과 같이 A 를 사용하여 k번째 경계선에 의해 둘러싸인 경계선, 혹은 이를 둘러싸는 경계선을 찾을 수 있습니다.
bwboundaries 함수는 제이콥(Jacob)의 중지 조건에 의해 수정된 무어-이웃(Moore-Neighbor) 추적 알고리즘을 구현합니다. 이 함수는 Digital Image Processing Using MATLAB(저자: Gonzalez, R. C., R. E. Woods, S. L. Eddins)의 초판(New Jersey, Pearson Prentice Hall, 2004)에 나와 있는 boundaries 함수를 기반으로 합니다.
참고 문헌
[1] Gonzalez, R. C., R. E. Woods, and S. L. Eddins, Digital Image Processing Using MATLAB, New Jersey, Pearson Prentice Hall, 2004.
확장 기능
C/C++ 코드 생성
MATLAB® Coder™를 사용하여 C 코드나 C++ 코드를 생성할 수 있습니다.
사용법 관련 참고 및 제한 사항:
bwboundaries 함수는 C 코드 생성을 지원합니다( MATLAB ® Coder™ 가 필요함). 일반적인 MATLAB Host Computer 대상 플랫폼을 선택할 경우 bwboundaries 함수는 미리 컴파일된 플랫폼별 공유 라이브러리를 사용하는 코드를 생성합니다. 공유 라이브러리를 사용하면 성능 최적화가 유지되지만 코드를 생성할 수 있는 대상 플랫폼이 제한됩니다. 자세한 내용은 Types of Code Generation Support in Image Processing Toolbox 항목을 참조하십시오.
JVM이란
JVM 구조
Class Loader
- 사용자가 코드를 작성하면 위의 그림과 같이 Stock.java 파일이 작성
- 이 .java 소스를 Java Compiler를 통해 Stock.class(바이트코드)로 생성
- 이렇게 생성된 클래스 파일들을 운영체제로 부터 할당 받은 메모리 영역인 Runtime Data Area로 적재하는 역할 수행
- 자바 어플리케이션이 실행중일 때 수행
개발자가 작성한 소스 코드를 바이너리 코드로 변환하는 과정 👉 목적 파일이 생성
이러한 작업을 해주는 프로그램 : 컴파일러(Compiler)
Execution Engine
- Class Loader에 의해 메모리에 적재된 클래스(바이트코드)들을 기계어로 변경해 명령어 단위로 실행
- 인터프리터(interpreter) 방식 : 명령어를 하나하나 실행
- JIT(Just-In-Time) 컴파일러를 이용하는 방식 : 적절한 시간에 전체 바이트 코드를 네이티브 코드로 변경해 Execution Engine이 네이티브 코드를 실행하는 것으로, 성능을 높이는 방식.
출처 : https://aboullaite.me/understanding-jit-compiler-just-in-time-compiler/
Native Code(=Unmanaged Code) : 컴파일하면 OS에서 해석가능한 기계어로 번역된 코드
Managed Code : OS가 바로 이해할 수 있는 바이너리가 아닌, 임시 코드(IL, Intermediate Language)로 변환시키는 것으로, IL 코드가 실행되기 위해 JIT 컴파일러가 OS용 코드로 변환. (JIT 컴파일러는 OS마다 다름)Quotex.io: 바이너리 옵션에서 추세선을 사용하는 방법
이 기사에서는 바이너리 옵션 거래에서 추세선의 사용에 대해 논의할 것입니다. 바이너리 옵션 거래에서 추세선을 사용하는 방법을 이해하면 거래할 때 정보에 입각한 결정을 내리고 시장 상황을 더 잘 이해할 수 있습니다.
당신이 묻는 추세선은 무엇입니까?
추세선은 시장(Forex, 주식, 바이너리 옵션 또는 암호화 거래 차트)의 피벗 고점 또는 피벗 저점 아래에 그려진 선으로 가격의 지배적인 방향을 보여줍니다.
간단히 말해서 추세선은 지지선과 저항선을 시각적으로 표현한 것입니다.
추세선을 사용하는 이유
추세선은 방향성 거래의 시장 움직임을 분석하는 데 이상적입니다.
과거 피벗 고점 또는 저점에 대한 추세선을 플로팅하여 traders는 시장이 역전되거나 돌파할 가능성이 있는 곳을 식별할 수 있습니다. 어떤 시나리오에 대해서도 적절하게 준비합니다.
추세선은 또한 다음을 결정하는 데 이상적입니다. trade 가치가있다. 시장이 어디에 반응할지 이해함으로써, traders는 언제 진입하고 퇴장할지에 대해 더 나은 결정을 내릴 수 있습니다. trade.
과테말라의 Marvin R.은 Trade이번주 r 쿼텍스 플랫폼, 그는 $16,909를 벌었습니다! 치하 훌륭한 결과에 대해 Marvin R.에게 그리고 우리는 모두가 성공적인 거래를 하기를 바랍니다!
이 기사를 계속 읽으면서 추세선 거래, 특히 바이너리 옵션 거래에서 추세선을 사용하는 방법에 대해 자세히 알게 될 것입니다.
이 게시물은 또한 바이너리 옵션에서 추세선이 작동하는 방식에 대한 이상적인 예와 함께 일상 거래에서 추세선을 사용하는 이점에 대해 설명합니다.
추세선은 어떻게 작동합니까?
바이너리 옵션을 거래하는 동안 추세선을 사용하는 아이디어는 둘 중 하나를 결정하기 전에 시장의 전반적인 방향을 파악하는 것입니다. trade 위 또는 아래.
바이너리 옵션으로 trader, 일련의 가격을 함께 연결하여 차트에 이러한 인식 가능한 선을 그려 시장이 Swift가 제공하는 여러 포인터 타입들과 동작 방식 Swift가 제공하는 여러 포인터 타입들과 동작 방식 상승, 하락 또는 횡보 추세인지 알 수 있습니다.
- 데모 계정
- Trade $ 1로
- 최대 98% RoR
- $10 최소 보증금
라인이 수평이고 가격 아래에 있으면 복제하고 가격의 맨 위로 이동하여 아래 이미지와 같이 채널을 형성해야 합니다.
이렇게 하면 자산을 매수할 때와 매도할 때를 아는 데 도움이 됩니다.
가격이 지지 추세선(하단 선)에서 반등할 때 매수하고 가격이 저항 추세선(상단 선)에서 반등할 때 매도할 수 있습니다.
반대로 가격이 저항선에서 돌파하고 지속 패턴을 보일 때 매수하고 지지선에서 돌파하고 지속적인 하락 신호를 보일 때 매도할 수 있습니다.
참고: – 추세선의 가격 할인 및 바운스는 위쪽 또는 아래쪽 추세선에서도 작동합니다.
Qx 브로커에서 추세선을 그리는 방법
Quotex에서 추세선을 그리는 것은 새 계정을 만드는 것만큼 쉽습니다. Quotex.io. 시간도 걸리지 않고 간단하고 간단합니다.
휴대폰을 사용하더라도 trade, 노트북 또는 태블릿에서 이 간단한 단계를 통해 차트에 추세선을 그릴 수 있습니다.
1단계: Quotex에 로그인
Quotex에 이미 계정이 있는 경우 다음 중 하나를 사용하여 로그인하십시오. Quotex 모바일 앱, 또는 웹 브라우저.
등록에 사용한 방법에 따라 Facebook, Apple, VK, Gmail 또는 이메일을 사용하여 계정에 액세스할 수 있습니다.
2단계: 차트를 일본 촛대로 변경
Quotex에는 4가지 차트 유형이 있습니다. 즉: –
- 막대 차트.
- 일본 촛대 차트.
- 하이켄 아시 차트.
- 영역 차트.
4가지 차트 유형 모두 거래 결정을 내리는 데 사용할 수 있는 중요한 데이터를 보여주지만 일본 촛대 차트는 다른 3가지 유형이 아닌 가격 조치에서 제공하는 추가 세부 정보 때문에 선호됩니다.
Quotex 플랫폼의 왼쪽 하단 모서리에 있는 차트 도구를 클릭하여 XNUMX개의 차트 간에 이동할 수 있습니다.
3단계: 차트에 추세선 그리기
먼저 "를 클릭하여 차트에 추세선을 그릴 수 있습니다.그리기 도구 탭"를 인용하고 추세선을 선택합니다.
다음으로 시장에서 피벗 고점 또는 피벗 저점을 식별하고 추세선을 끌어서 연결합니다.
- 데모 계정
- Trade $ 1로
- 최대 98% RoR
- $10 최소 보증금
이 피벗을 연결하면 상승, 하락 또는 옆으로 인식할 수 있는 선이 생성됩니다.
추세선이 상승하면 상승 추세입니다. 추세선이 아래쪽이면 하락 추세이고 추세선이 옆으로 있으면 범위 조정 시장입니다.
Quotex에서 추세선을 지원 및 저항 수준으로 사용하는 방법
올바르게 그려지면 추세선은 피벗 고점 또는 피벗 저점 아래를 연결합니다. 그들이 피벗 저점을 연결하면 이러한 추세선은 지원 영역으로 간주됩니다.
반면에 피벗 고점을 연결하는 추세선은 저항 수준으로 간주됩니다.
지원 및 저항 수준은 무엇입니까?
'지지'와 '저항'은 특정 지역의 시장을 제한하는 것처럼 보이는 가격 차트의 두 수준에 대한 용어입니다.
간단히 말해서 지원 수준은 구매자가 자산을 구매할 의향이 있는 가격대입니다. 반면에 저항 수준은 판매자가 자산을 판매할 의사가 있는 가격대입니다.
지지 수준은 가격이 정기적으로 하락을 멈추고 다시 반등하는 곳입니다., 저항 수준은 가격이 일반적으로 상승을 멈추고 다시 하락하는 곳입니다.
가격이 지지선이나 저항선을 돌파하면 구매자 또는 판매자가 이전 지점보다 더 강하고 시장이 그 방향으로 계속 움직일 가능성이 있음을 나타냅니다.
Quotex의 차트에 추세선을 그리면 가장 많은 피벗 최저점에 닿는 선이 지원 수준입니다. 반대로, 위의 이미지와 같이 대부분의 피벗 고점에 닿는 추세선을 그릴 수 있으며 이것이 저항 수준이 됩니다.
추세선의 유형
추세선에는 오름차순, 내림차순 및 평면의 세 가지 주요 유형이 있습니다.
각각 고유한 사용 사례가 있으므로 거래 전략에 가장 적합한 것이 무엇인지 아는 것이 중요합니다. 추세선의 각 유형과 바이너리 옵션 거래에서 사용하는 방법을 살펴보겠습니다.
ㅏ). 상승/상승 추세선(고저점)
앞서 언급했듯이 상승 추세 또는 상승 추세선은 차트에서 지속적으로 더 높은 고점과 더 높은 저점을 만나는 추세선입니다.
이것이 차트에 상승 추세가 표시되는 방식입니다.
비). 하락세(낮은 고점)
하락 추세는 기초 자산의 가격이 지속적으로 저항 수준 아래로 떨어질 때 발생합니다.
바이너리 옵션에서 하락 추세는 더 낮은 고점과 더 낮은 저점의 출현으로 식별될 수 있습니다. 자산 가격이 저항선 아래로 떨어지면 하락세라고 합니다.
바이너리 옵션의 하락세를 식별할 때 다음 사항을 염두에 두는 것이 중요합니다.
- 자산은 시간이 지남에 따라 몇 차례 더 낮은 고점과 더 낮은 저점을 경험했을 수 있습니다. 이는 추세가 존재할 가능성이 있음을 나타냅니다.
- 자산 가격이 여러 지원 수준과 저항 수준 아래로 떨어졌다면 강한 하락세가 존재할 가능성이 높습니다.
차트에 이상적인 하락 추세가 표시되는 방법은 다음과 같습니다.
씨). 횡보 추세(범위)
횡보 추세는 다음을 제공하기 때문에 가장 인기 있는 거래 패턴 중 하나입니다. traders 이익을 위한 많은 기회.
자산 가격이 정의된 두 지점(수평 추세선) 사이를 이동할 때 횡보 추세라고 합니다.
이 수평적 가격 움직임을 상향 또는 하향이라고도 합니다.
- 데모 계정
- Trade $ 1로
- 최대 98% RoR
- $10 최소 보증금
바이너리 옵션에서 횡보 추세는 종종 다음을 제공합니다. trade더 나은 수익 기회가 있는 rs trades 상승 또는 하락 추세보다.
횡보 추세를 식별하려면 먼저 차트에서 정의된 두 지점 사이에서 가격이 이동했는지 확인합니다.
이 패턴을 확인했으면 추세가 계속되고 있음을 시사하는 지표를 찾으십시오. 관찰할 수 있는 일부 지표에는 이동 평균, MACD 라인 및 볼린저 밴드가 있습니다.
추세가 계속되면 그리기 도구에서 추세선을 선택하고 차트에서 피벗 고점과 피벗 저점을 결합하여 지원 및 저항 수준을 시각적으로 표현합니다.
여러분은 trade 가격이 지지선에서 반등하면 상승하고 저항선에서 반등하면 매도합니다.
추세선을 그릴 때 Quotex.io
가장 일반적인 질문 중 하나 traders ask는 바이너리 옵션에서 추세선을 그릴 때입니다. 대부분의 거래와 마찬가지로 답은 자신의 개별 거래 스타일과 전략에 따라 다릅니다.
그러나 바이너리 옵션을 거래할 때 따라야 하는 몇 가지 일반적인 추세가 있습니다.
1) 유가증권이나 자산이 상승세를 보일 때, 일반적으로 추세선을 따라 위로 올라갈 가치가 있습니다. 유가 증권이나 자산이 하락 추세라면 일반적으로 추세선을 따라가는 것이 좋습니다. 자산의 방향에 따라 진입 및 퇴장을 돕기 위해 추세선을 그릴 수 있습니다.
2) 유가증권이나 자산이 고립된 움직임을 보이는 경우 (즉, 더 큰 추세의 일부가 아님) 추세선을 무시하고 trade 자신의 분석과 직관을 바탕으로
그러나 유가 증권 또는 자산이 더 큰 추세 내에서 움직이고 이전 상승 추세/하향 추세를 통합/재개하는 것으로 보인다면 일반적으로 추세선을 따라갈 가치가 있습니다.
과테말라의 Marvin R.은 Trade이번주 r 쿼텍스 플랫폼, 그는 $16,909를 벌었습니다! 치하 훌륭한 결과에 대해 Marvin R.에게 그리고 우리는 모두가 성공적인 거래를 하기를 바랍니다!
3) 추세선은 지침일 뿐입니다. – 모든 상황에 항상 적용되는 엄격하고 빠른 규칙이 아닙니다. 모든 재무 분석과 마찬가지로 추세선을 그릴 때 자신의 판단을 사용하고 trade 따라서!
Quotex의 추세선 신호
추세선은 차트에서 가치 영역을 식별하는 데 도움이 될 수 있습니다. 이러한 영역은 지원 영역 또는 영역 또는 저항 영역이 될 수 있습니다.
이러한 수준을 확인하고 다음을 수행할지 여부에 따라 trade 바운스 또는 브레이크 아웃, 당신은 당신의 신호를 얻을 것입니다.
거래 반전에 대한 추세선 신호의 예는 trade 가격이 추세선 지지 또는 추세선 저항에 도달하고 바운스될 때.
다른 지표를 사용하거나 가격 조치를 사용하여 바운스의 유효성을 확인할 수 있습니다.
반면에 trade 가격이 지지선이나 저항선을 돌파할 때까지 기다리면서 돌파 신호를 보냅니다. 재시험을 기다리면 휴식시간의 유효성을 확인할 수 있습니다. trade.
추세선은 바이너리 옵션에 대한 더 나은 투자 결정을 내리는 데 도움이 되는 강력한 도구입니다. 나는 당신이 많은 것을 배웠다는 것을 의미하기 때문에 당신이 이 시점까지 이 포스트를 읽었기를 바랍니다.
추세선에 대해 궁금한 점이 있습니까? 언제든지 댓글에 남겨주세요. 빠른 시일 내에 기꺼이 답변해 드리겠습니다.
추신: – Quotex에 계정이 없는 경우 여기에 하나를 만드십시오. 또한 프로모션 코드 "JOON"을 사용하여 $50 이상 입금 시 100% 보너스를 받으세요.
0 개 댓글