2016-08-18 2 views
1

32 비트 운영 체제에서 XMM0 128 비트 레지스터를 사용하여 char 포인터 배열에서 메모리를로드/저장하려고합니다. 내가 뭘하려주소 위치에서 XMM 레지스터로드하기

매우 간단합니다 :

int main() { 
    char *data = new char[33]; 
    for (int i = 0; i < 32; i++) 
     data[i] = 'a'; 
    data[32] = 0; 
    ASM 
    { 
     movdqu xmm0,[data] 
    } 

    delete[] data; 
} 

문제는이 작동하지 않는다는 것입니다. 처음으로 나는 내가 가진 Win32 응용 프로그램을 디버깅 :

XMM0 = 0024F8380000000000F818E30055F158

내가 가진 내가 그것을 디버깅 두 번째 시간 :

XMM0 = 0043FD6800000000002C18E3008CF158

그래서 라인이있는 뭔가가 있어야합니다 :

movdqu xmm0,[data] 

내가 대신 사용하여 시도 :

movdqu xmm0,data 

하지만이 같은 결과를 얻었다.

내가 문제라고 생각한 것은 주소의 데이터 대신 주소를 복사한다는 것입니다. 그러나 xmm0 레지스터에 표시된 값은 32 비트 주소에 비해 너무 크기 때문에 다른 주소에서 메모리를 복사해야합니다.

나는 또한 인터넷에서 발견 된 다른 지침을 시도했지만 동일한 결과를 얻었다.

포인터를 전달하는 방식입니까, 아니면 xmm 기본 사항에 대한 오해입니까?

설명이있는 유효한 해결책을 알려드립니다. 내가 솔루션 (마지막 후 3 시간) 발견하더라도

, 나는 아직도 설명 하시겠습니까 :

ASM 
    { 
     push eax 
     mov eax,data 
     movdqu xmm0,[eax] 
     pop eax 
    } 

가 왜 포인터를 통과해야 32 비트에 등록?

+0

'data'는 포인터입니다. –

+0

'[data]'가있는 원래의 게시물에서와 같이 포인터가있는 new/delete 대신에 지역 변수'char data [33];'를 직접 사용할 수 있습니까? 지금은 디버그 할 수 없지만, 컴파일 된 소스를 상상할 수있는 것처럼 이것이 작동한다고 생각합니다. 지금 당황하고있는 것은 무엇입니까? char + data와 C++의 차이점은 무엇입니까? C + +의 관점에서 그들은 동등한 것으로 보입니다. 나는 아마 뭔가를 바라보고있을거야. (그리고 두 번째 버전에서'mov eax, data'는'mov eax, [data]'로 컴파일됩니다.) – Ped7g

+4

x86에는 "메모리 간접"주소 지정 모드가 없습니다. 포인터를'xmm0'에로드하고 있습니다. 'xmm0'는 포인터보다 크기 때문에, 포인터가 저장된 곳의 끝 부분에서 메모리의 가비지 바이트를 읽는 것입니다. –

답변

1
#include <iostream> 

int main() 
{ 
    char *dataptr = new char[33]; 
    char datalocal[33]; 
    dataptr[0] = 'a'; dataptr[1] = 0; 
    datalocal[0] = 'a'; datalocal[1] = 0; 
    printf("%p %p %c\n", dataptr, &dataptr, dataptr[0]); 
    printf("%p %p %c\n", datalocal, &datalocal, datalocal[0]); 
    delete[] dataptr; 
} 

출력 : 우리가 알 수 있듯이

0xd38050 0x7635bd709448 a 
0x7635bd709450 0x7635bd709450 a 

동적 포인터 data 포인터 변수 (32 비트 또는 0x7635BD709448에서 64 비트) 힙, 0xD38050에 대한 포인터를 포함하는, 정말.

로컬 변수는 주소가 0x7635BD709450 인 33 바이트 길이의 직접 버퍼입니다.

그러나 datalocalchar * 값으로도 작동합니다.

저는이 형식적인 C++ 설명이 다소 혼란 스럽습니다.C++ 코드를 작성하는 동안 이것은 매우 자연스럽고 dataptr [0]은 힙 메모리의 첫 번째 요소 (즉, dataptr을 두 번 참조 해제)이지만 어셈블러에서 포인터 변수의 주소 인 dataptr이라는 참된 특성을 볼 수 있습니다. 따라서 mov eax,[data] = 덤프 포인터를로드하고 0xD38050을 사용하여 eax을로드 한 다음 [eax]을 사용하여 0xD38050의 내용을 XMM0에로드 할 수 있습니다.

로컬 변수에는 주소가있는 변수가 없습니다. 심볼 datalocal은 이미 첫 번째 요소의 주소이므로 movdqu xmm0,[data]이 작동합니다.

"잘못된"경우에도 여전히 수행 할 수 있습니다 movdqu xmm0,[data]; 32 비트 변수에서 128 비트를로드하는 것은 CPU의 문제가 아닙니다. 이것은 단순히 32 비트를 넘어서 계속 읽으며 다른 변수/코드에 속한 또 다른 96 비트를 읽습니다. 메모리 경계 근처에서 응용 프로그램의 마지막 메모리 페이지 인 경우 잘못된 액세스로 인해 충돌이 발생합니다.


주석이 몇 번 언급되었습니다. 그것은 유효한 지적입니다. movdqu을 통해 메모리에 액세스하려면 정렬되어야합니다. C++ 컴파일러 내장 함수를 확인하십시오. 비주얼 스튜디오의 경우이 작업을해야합니다 : 내 C++ 해석에 대해

__declspec(align(16)) char datalocal[33]; 
char *dataptr = _aligned_malloc(33, 16); 
_aligned_free(dataptr); 

을 : 어쩌면 나는 처음부터이 잘못되었다.

dataptr은 dataptr 심볼의 값, 즉 해당 힙 주소입니다. 그런 다음 dataptr[0]은 할당 된 메모리의 첫 번째 요소에 액세스하여 힙 주소를 역 참조합니다. &dataptrdataptr 값의 주소입니다. 이는 dataptr = nullptr;과 같은 구문에서도 의미가 있습니다. 여기서 nullptr 값을 dataptr 변수에 저장하고 dataptr 기호 주소를 덮어 쓰지 않습니다. 이 배열 변수로 datalocal[]

는 기본적으로 datalocal = 'a';에 같은 순수한 datalocal를 액세스하는 것은 의미가 없다, 그래서 당신은 항상 [] 인덱스를 제공해야합니다. 그리고 &datalocal은 그러한 배열의 주소입니다. 순수 datalocalchar * 타입을 갖는 배열 등을 사용하는 더 쉬운 포인트 수학을위한 별칭 지름길이지만 순수한 datalocal이 구문 오류를 던지더라도 여전히 포인터로 &datalocal을 사용하여 C++ 코드를 작성할 수 있습니다. 요소의 경우 datalocal[..]),이 코드는 dataptr 논리와 완전히 일치합니다.

결론 : 어셈블리 언어 [data]data 값 (new에 의해 반환 된 포인터)을로드하고 있기 때문에 처음부터 잘못된 예가있었습니다.

이 내 자신의 설명, 그리고 지금 일부 C++ 전문가 :) ... 와서보기의 공식적인 관점에서 조각을 찢어 것)) 코드와

+0

대부분의 컨텍스트에서 (예 : [함수 arg로 전달] (http://stackoverflow.com/questions/38800044/what-kind-of-c11-data-type-is-an-array-according-to-the- amd64-abi # comment64984890_38800044),'+'나'[]'와 같은 연산자를 사용할 때, 배열은 포인터처럼 작동합니다. 그러나 주소는 어디에도 저장되지 않습니다. 즉각적인 상수와 같습니다. 또는 스택 포인터에서 컴파일 타임 상수 오프셋. 그러나 포인터 변수 *는 실제로 메모리 나 레지스터에 포인터를 저장합니다.BTW,'& datalocal'은 경고를 주지만'& datalocal [0]'과 같은 코드로 컴파일합니다. https://godbolt.org/g/05S5XS –

+0

'movdqu'가 정렬되지 않은 액세스라고 생각 했습니까? 그렇다면 정렬 할 필요가 없습니다. 그것이 일치하는 것으로 알려졌다면 나는 movdqa –

3

문제는 data는 포인터입니다. 어셈블리 코드 movdqu xmm0,[data]data의 주소에있는 16 바이트를 레지스터 xmm0에로드합니다. 이것은 포인터의 값을 구성하는 4 또는 8 바이트와 그 다음에 오는 모든 바이트를 의미합니다. 포인터 주소가 메모리에 올바르게 정렬되어 있으면 다행입니다. 그렇지 않으면 세그먼트 화 오류가 발생합니다. 이 정렬을 보장하는 것은 없습니다.

자동 배열 을 사용하는 다른 방법으로는 배열 문제를 해결할 수 있지만 배열 문제는 해결되지 않지만 컴파일러가 자동 저장 장치와 배열을 정렬하는 방법에 따라 위반할 수 있습니다. 다시 말하지만, 적절한 정렬을 보장하지 않습니다.

당신이 찾은 해결책은 아마도 좋은 접근이지만, malloc()과 달리 new에 의해 반환 된 포인터가 모든 정렬에 유효한지 확실하지 않습니다.

이 모든 경우에 작동합니다 :

피터 코르에 의해 주석으로
#include <stdlib.h> 

int main(void) { 
    char *data = malloc(33); 
    for (int i = 0; i < 32; i++) { 
     data[i] = 'a'; 
    } 
    data[32] = 0; 
    __asm { 
     mov eax, data 
     movdqu xmm0, [eax] 
    } 
    free(data); 
    return 0; 
} 

, 이런 종류의 일, 즉 mm_loadu_si128에 대한 내장 함수를 사용하는 것이 훨씬 낫다. 주된 두 가지 이유가 있습니다. 첫째, 64 비트 빌드에서는 인라인 어셈블리가 지원되지 않으므로 내장 함수를 사용하면 코드가 약간 더 휴대 가능 해집니다. 둘째, 컴파일러는 인라인 어셈블리를 최적화하는 데 상대적으로 열악한 작업을 수행하며, 특히 무의미한 메모리 저장소 및로드가 많이 발생하는 경향이 있습니다. 컴파일러는 내장 함수를 최적화하는 작업을 훨씬 더 잘 수행하므로 코드를 더 빠르게 실행할 수 있습니다 (인라인 어셈블리를 사용하는 전체 지점).

+0

포기하지 않으셔서 미안하지만 15 명의 담당자가 없다고 제안 할 것입니다 : X – user2377766

+0

@ user2377766 : 빨리 올 것이 오 ;-) – chqrlie

+2

푸시/팝 안을 사용하지 마십시오. 인라인 asm 문. MSVC는 사용자의 asm을 읽고 사용하는 레지스터를 저장/복원합니다. 더 중요한 것은 MSVC 인라인 ASM을 사용하지 마십시오. intrinsic을 사용하면 더 나은 결과를 얻을 수 있습니다. –