문제

나는 물었다 의문 C 형 크기에 대해 꽤 좋은 답변을 얻었지만 내 목적에 유용한 질문을 잘 공식화하지 못할 수도 있다는 것을 깨달았습니다.

저의 배경은 소프트웨어 엔지니어로 이동하기 전에 컴퓨터 엔지니어의 배경이므로 컴퓨터 아키텍처를 좋아하고 항상 VM을 만드는 것에 대해 생각하고 있습니다. 방금 Java에서 VM을 만드는 흥미로운 프로젝트를 마쳤습니다. 그러나 지금은 소스를 오픈 소스 할 수없는 몇 가지 법적 문제가 있으며 현재 자유 시간이 있습니다. 그래서 나는 재미 있고 교육을 위해 C에서 또 다른 VM을 만들 수 있는지 알고 싶습니다.

문제는 내가 Trivia C 문제를 마지막으로 썼을 때 C 프로그램이 아니라는 것입니다. 나는 Pascal, Delphi, 그리고 현재 Java와 PHP 프로그래머였습니다.

내가 예측할 수있는 많은 장애물이 있으며, 나는 하나를 다루려고 노력하고 있으며 기존 라이브러리에 액세스하고 있습니다 (Java에서는 반사 가이 문제를 해결합니다).

데이터 버퍼 (스택과 유사)를 사용하여이를 해결할 계획입니다. 내 VM의 클라이언트는 기본 기능에 대한 포인터를 제공하기 전에이 스택에 데이터를 넣을 수 있도록 프로그램 할 수 있습니다.

int main(void) {
    // Prepare stack
    int   aStackSize = 1024*4;
    char *aStackData = malloc(aStackSize);

    // Initialise stack
    VMStack aStack;
    VMStack_Initialize(&aStack, (char *)aStackData, aStackSize);

    // Push in the parameters
    char *Params = VMStack_CurrentPointer(&aStack);
    VMStack_Push_int   (&aStack, 10  ); // Push an int
    VMStack_Push_double(&aStack, 15.3); // Push a double

    // Prepare space for the expected return
    char *Result = VMStack_CurrentPointer(&aStack);
    VMStack_Push_double(&aStack, 0.0); // Push an empty double for result

    // Execute
    void (*NativeFunction)(char*, char*) = &Plus;
    NativeFunction(Params, Result); // Call the function

    // Show the result
    double ResultValue = VMStack_Pull_double(&aStack); // Get the result
    printf("Result:  %5.2f\n", ResultValue);               // Print the result

    // Remove the previous parameters
    VMStack_Pull_double(&aStack); // Pull to clear space of the parameter
    VMStack_Pull_int   (&aStack); // Pull to clear space of the parameter

    // Just to be sure, print out the pointer and see if it is `0`
    printf("Pointer: %d\n", aStack.Pointer);

    free(aStackData);
    return EXIT_SUCCESS;
}

기본 기능의 푸시, 당기기 및 호출은 바이트 코드에 의해 트리거 될 수 있습니다 (즉, VM이 나중에 만드는 방식).

완전성을 위해 (기계에서 시도해 볼 수 있도록), 여기에 스택 코드가 있습니다.

typedef struct {
    int  Pointer;
    int  Size;
    char *Data;
} VMStack;

inline void   VMStack_Initialize(VMStack *pStack, char *pData, int pSize) __attribute__((always_inline));
inline char   *VMStack_CurrentPointer(VMStack *pStack)                    __attribute__((always_inline));
inline void   VMStack_Push_int(VMStack *pStack, int pData)                __attribute__((always_inline));
inline void   VMStack_Push_double(VMStack *pStack, double pData)          __attribute__((always_inline));
inline int    VMStack_Pull_int(VMStack *pStack)                           __attribute__((always_inline));
inline double VMStack_Pull_double(VMStack *pStack)                        __attribute__((always_inline));

inline void VMStack_Initialize(VMStack *pStack, char *pData, int pSize) {
    pStack->Pointer = 0;
    pStack->Data    = pData;
    pStack->Size    = pSize;
}

inline char *VMStack_CurrentPointer(VMStack *pStack) {
    return (char *)(pStack->Pointer + pStack->Data);
}

inline void VMStack_Push_int(VMStack *pStack, int pData) {
    *(int *)(pStack->Data + pStack->Pointer) = pData;
    pStack->Pointer += sizeof pData; // Should check the overflow
}
inline void VMStack_Push_double(VMStack *pStack, double pData) {
    *(double *)(pStack->Data + pStack->Pointer) = pData;
    pStack->Pointer += sizeof pData; // Should check the overflow
}

inline int VMStack_Pull_int(VMStack *pStack) {
    pStack->Pointer -= sizeof(int);// Should check the underflow
    return *((int *)(pStack->Data + pStack->Pointer));
}
inline double VMStack_Pull_double(VMStack *pStack) {
    pStack->Pointer -= sizeof(double);// Should check the underflow
    return *((double *)(pStack->Data + pStack->Pointer));
}

기본 기능 측면에서 테스트 목적을 위해 다음을 만들었습니다.

// These two structures are there so that Plus will not need to access its parameter using
//    arithmetic-pointer operation (to reduce mistake and hopefully for better speed).
typedef struct {
    int    A;
    double B;
} Data;
typedef struct {
    double D;
} DDouble;

// 여기에는 void printData (data *pdata, ddouble *presult)를 표시하는 도우미 기능이 있습니다. {printf ( " %5.2f + %5.2f = %5.2f n", pdata-> a *1.0, pdata-> b , presult-> d); }

// 일부 기본 함수 void plus (char * pparams, char * presult) {data * d = (data *) pparams; // 산술 포인터 작동없이 데이터에 액세스 할 수 있습니다. // 반환 dd-> d = d-> a + d-> b; printData (d, dd); }

실행되면 위의 코드가 반환됩니다.

10.00 + 15.30 = 25.30
Result:  25.30
Pointer: 0

이것은 내 컴퓨터에서 잘 작동합니다 (Linux x86 32bits GCC-C99). 이것이 다른 OS/아키텍처에서도 작동한다면 매우 좋을 것입니다. 그러나 우리가 알아야 할 메모리 관련 발행이 3 개 이상 있습니다.

1). 데이터 크기 - 동일한 아키텍처에서 동일한 컴파일러를 사용하여 VM 및 기본 기능을 모두 컴파일하면 크기 유형이 동일해야합니다.

2). Endianness- 데이터 크기와 동일합니다.

삼). 메모리 정렬 - 패딩 바이 테스가 구조물에 추가 될 수 있기 때문에 문제가되지만 매개 변수 스택을 준비 할 때 동기화하기가 어렵습니다 (하드 코딩을 제외하고 패딩이 추가되는 방법을 알 수있는 방법이 없습니다).

내 질문은 다음과 같습니다.

1). 유형의 크기를 알고 있다면 Push and Pull 함수를 수정하여 Struct Padding과 정확히 동기화하는 방법이 있습니까? (컴파일러가 DataSize 및 Endians 문제와 같은 문제를 처리하도록 수정하십시오).

2). 구조를 하나씩 포장하는 경우 (사용 #pragma pack(1)); (2.1) 성과 페널티가 수용 가능합니까? 그리고 (2.2) 프로그램 안정성이 위험에 처할까요?

삼). 2,4 또는 8으로 패딩하는 것은 어떻습니까? 일반 32 또는 64 비트 시스템에 좋은 것은 무엇입니까?

4). 정확한 패딩 알고리즘에 대한 문서로 안내해 주시겠습니까?

5). 더 좋은 방법이 있습니까?

참고 : 크로스 플랫폼은 나의 궁극적 인 목표는 아니지만 저항 할 수 없습니다. 또한, 성능은 너무 추악하지 않은 즉시 내 목표가 아닙니다. 이 모든 것은 재미와 학습을위한 것입니다.

내 영어와 매우 긴 게시물에 대해 죄송합니다.

모두에게 미리 감사드립니다.

도움이 되었습니까?

해결책

접선 의견

이 첫 번째 항목은 당신이 묻는 질문에 접하는 것이지만 ...

// Execute
void (*NativeFunction)(char*, char*) = &Plus;
NativeFunction(Params, Result); // Call the function

나는 당신이 아마도 'char *'대신 'void *'를 사용해야한다고 생각합니다. 또한 함수 포인터 유형에 대한 typedef도 있습니다.

typedef void (*Operator)(void *params, void *result);

그런 다음 쓸 수 있습니다.

Operator NativeFunction = Plus;

실제 함수도 수정되지만 매우 약간만 수정됩니다.

void Plus(void *pParams, void *pResult)

또한 사소한 이름 지정 문제가 있습니다.이 기능은 일반적인 목적이 아닌 'intplusdoublegivesdouble ()'입니다.


질문에 대한 직접적인 답변

1). 유형의 크기를 알고 있다면 Push and Pull 함수를 수정하여 Struct Padding과 정확히 동기화하는 방법이 있습니까? (컴파일러가 DataSize 및 Endians 문제와 같은 문제를 처리하도록 수정하십시오).

그렇게하는 쉬운 방법은 없습니다. 예를 들어, 고려하십시오.

struct Type1
{
     unsigned char byte;
     int           number;
};
struct Type2
{
     unsigned char byte;
     double        number;
};

일부 아키텍처 (예 : 32 비트 또는 64 비트 SPARC)에서 Type1 구조는 4 바이트 경계에서 '숫자'가 정렬되지만 Type2 구조는 8 바이트 경계에 '숫자'가 정렬됩니다 ( 16 바이트 경계에 '긴 이중'이있을 수 있습니다). '개별 요소 푸시'전략은 '바이트'값을 누른 후 스택 포인터를 1만큼 부딪칩니다. 따라서 스택 포인터가 아직 적절하지 않은 경우 스택 포인터를 3 또는 7만큼 3 또는 7으로 움직 이려고합니다. 정렬. VM 설명의 일부는 특정 유형에 필요한 정렬입니다. 해당 푸시 코드는 푸시하기 전에 올바른 정렬을 보장해야합니다.

2). 구조를 하나씩 포장하는 경우 (#Pragma Pack (1) 사용); (2.1) 성과 페널티가 수용 가능합니까? 그리고 (2.2) 프로그램 안정성이 위험에 처할까요?

X86 및 X86_64 머신에서 데이터를 포장하면 잘못 정렬 된 데이터 액세스에 대한 성능 페널티가 발생합니다. SPARC와 같은 기계에서 또는 powerpc(당 메키), 버스 오류가 발생하거나 대신 유사한 것입니다. 적절한 정렬로 데이터에 액세스해야합니다. 성능 비용으로 메모리 공간을 절약 할 수 있습니다. 우주의 한계 비용으로 성능 (여기서는 충돌 대신 올바르게 수행하는 것 ')을 보장하는 것이 좋습니다.

삼). 2,4 또는 8으로 패딩하는 것은 어떻습니까? 일반 32 또는 64 비트 시스템에 좋은 것은 무엇입니까?

SPARC에서는 N-Byte 기본 유형을 N-Byte 경계에 패드해야합니다. X86에서는 똑같이하면 최상의 성능을 얻게됩니다.

4). 정확한 패딩 알고리즘에 대한 문서로 안내해 주시겠습니까?

당신은 그것을 읽어야 할 것입니다 수동.

5). 더 좋은 방법이 있습니까?

단일 문자와 유형이있는 'type1'트릭은 정렬 요구 사항을 제공합니다. <stddef.h>:

offsetof(struct Type1, number)

글쎄, 나는 스택에 데이터를 포장하지 않을 것입니다 - 나는 최고의 성능을 제공하기 때문에 기본 정렬과 함께 일할 것입니다. 컴파일러 라이터는 구조에 패딩을 추가하지 않습니다. 그들은 건축에 '가장 잘 작동하기 때문에 그것을 넣었습니다. 당신이 더 잘 알고 있다고 결정하면, 당신은 일반적인 결과를 기대할 수 있습니다 - 때로는 실패하고 휴대용이 아닌 느린 프로그램.

또한 스택에 구조가 포함되어 있다고 가정하기 위해 연산자 기능에 코드를 작성할 것이라고 확신하지 않습니다. 올바른 오프셋과 유형이 무엇인지 알면서 매개 변수 인수를 통해 스택에서 값을 뽑을 것입니다. 정수와 더블을 밀면 정수와 더블을 당기는 것입니다 (또는 아마도 리버스 순서 - 더블과 int를 당기는 것). 비정상적인 VM을 계획하지 않는 한, 많은 기능에 많은 주장이 있습니다.

다른 팁

흥미로운 게시물과 당신이 많은 일을했음을 보여줍니다. 거의 이상적인 게시물입니다.

준비된 답변이 없으므로 저와 함께 부탁하십시오. 몇 가지 질문을해야 할 것입니다 : P

1). 유형의 크기를 알고 있다면 Push and Pull 함수를 수정하여 Struct Padding과 정확히 동기화하는 방법이 있습니까? (컴파일러가 DataSize 및 Endians 문제와 같은 문제를 처리하도록 수정하십시오).

이것은 성능 관점에서만 나온 것입니까? 기본 산술 유형과 함께 포인터를 소개 할 계획입니까?

2). 구조를 하나씩 포장하는 경우 (#Pragma Pack (1) 사용); (2.1) 성과 페널티가 수용 가능합니까? 그리고 (2.2) 프로그램 안정성이 위험에 처할까요?

이것은 구현 정의 된 것입니다. 플랫폼에서 믿을 수있는 것이 아닙니다.

삼). 2,4 또는 8으로 패딩하는 것은 어떻습니까? 일반 32 또는 64 비트 시스템에 좋은 것은 무엇입니까?

기본 단어 크기와 일치하는 값은 최적의 성능을 제공해야합니다.

4). 정확한 패딩 알고리즘에 대한 문서로 안내해 주시겠습니까?

나는 내 머리 꼭대기를 모른다. 그러나 나는 비슷한 코드를 보았다 이것 사용 중.

당신은 할 수 있습니다 변수의 속성을 지정합니다 GCC를 사용합니다 (여기에는 부르는 것도 있습니다 default_struct __attribute__((packed)) 패딩이 꺼집니다).

여기에는 몇 가지 좋은 질문이 있습니다. 많은 사람들이 중요한 디자인 문제에 얽매이지 만 대부분의 사람들에게는 우리가 당신이 무엇을하고 있는지 볼 수 있습니다 (내가 글을 쓰면서 글을 쓰면서 당신이 관심을 끌고 있음을 알 수 있습니다). 당신이 노력하고있는 것은 컴파일러 문제와 언어 설계 문제라는 것을 충분히 이해할 수 있습니다. 질문을하기가 어려워 지지만 이미 JNI에서 일하고 있다는 것은 희망이 있습니다 ...

우선, 나는 pragmas에서 벗어나려고 노력할 것입니다. 많은 사람들, 매우 많은 그것에 동의하지 않을 것입니다. 왜 문제에 대한 D 언어 위치에 대한 정당화를 참조하는지에 대한 정식 논의. 다른 하나는 코드에 묻힌 16 비트 포인터가 있습니다.

이 문제는 끝이없고 잘 연구되었으며 우리를 야당과 교내 무관심에 묻힐 가능성이 높습니다. 내가 읽는 것을 제안 할 수 있다면 Kenneth Louden의 홈페이지 인텔 아키텍처 매뉴얼뿐만 아니라. 나는 그것을 가지고있다, 나는 그것을 읽으려고 노력했다. 데이터 구조 정렬은 토론을 위해 제기 한 다른 많은 문제와 함께 역사적 컴파일러 과학에 깊이 묻혀 있으며 누가 알고있는 사람에 대해 당신을 가질 가능성이 높습니다. (예상 할 수없는 결과에 대한 속어 또는 관용적 인 결과)

그 말로 여기에 간다 :

  1. C 형 크기 어떤 유형 크기?
  2. 컴퓨터 엔지니어는 소프트웨어 엔지니어로 이동하기 전에마이크로 컨트롤러를 연구 한 적이 있습니까? Hava Don Lancaster의 작품을 살펴보십시오.
  3. Pascal, Delphi 및 현재 Java 및 PHP 프로그래머.그것들은 프로세서의 기본 기본 아키텍처에서 비교적으로 제거되지만 많은 사람들이 강력하고 기본적인 루틴을 작성하는 데 어떻게 사용될 수 있는지 보여 주거나 시도 할 것입니다. 나는 David Eck의 재귀 적 출신 파서를보고 문제에 대한 연구를 시작하는 방법을 정확히 알아 보는 것이 좋습니다. 또한 Kenneth Louden은 실제 컴파일러 인 "Tiny"를 구현했습니다. 얼마 전까지 만해도 Asm Dot Org라고 불렀던 것을 발견했습니다. 또한 대부분의 아키텍처에는 한 프로세서에서 다른 프로세서로 일관되지 않는 차이가 있습니다.
  4. 기존 라이브러리 액세스

주위에 많은 리브가 있으며, Java에는 좋은 것들이 있습니다. 나는 다른 사람들에 대해 모른다. 한 가지 방법은 lib를 쓰려고하는 것입니다. Java는 좋은 기지를 가지고 있으며 사람들이 더 나은 것을 생각해 내려고하는 공간을 떠납니다. Knuth-Morris-Pratt 등을 개선하는 것으로 시작하십시오. 시작할 장소가 부족하지 않습니다. 노력하다 컴퓨터 프로그래밍 알고리즘 디렉토리 그리고 확실히보십시오 알고리즘 및 데이터 구조 사전 NIST에서

  1. 항상_inline

반드시 DOV Bulka를 참조하십시오-근로자는 CS에서 박사 학위를 보유하고 있으며 시간 효율성 / 신뢰성 강화 등이있는 영역에서도 능숙한 저자이기도합니다. 실제로 중요한 문제에 대해 "오!

마감 참고로, 계측 및 제어는 설명대로 성취 된 프로그래밍 기술을 위해 실제 시장의 60% 이상을 구성합니다. 어떤 이유로, 우리는 대부분 비즈니스 모델에 대해 듣습니다. 신뢰할 수있는 출처에서 가지고있는 Tidbit 내부와 공유하겠습니다. 에서 10% ~ 60% 이상 실제 안전 및 재산 위험은 도둑, 도난 및 그런 종류의 것보다 차량 문제에서 비롯됩니다. "카운티 광물 추출 Faciltiy에서 90 일 Bustin Minerals!"에 대한 항소를 듣지 못할 것입니다. 교통 티켓의 경우 실제로 대부분의 사람들은 교통 인용이 (NA -USA) 클래스 4 경범죄임을 인식조차하지 않으며 실제로 분류 가능합니다.

당신이 좋은 일을 향한 좋은 발걸음을 내딛은 것처럼 들립니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top