Pergunta

Eu pedi um questão sobre tamanhos do tipo C que eu recebo uma resposta muito boa, mas eu percebi que eu não pode formular a questão muito bem para ser útil para o meu propósito.

Meu plano era de Engenheiro de Computação antes move para engenheiro de software assim que eu gosto arquiteturas de computadores e sempre pensando em fazer VM. Acabei de terminar um projeto interessante fazer VM em Java que estou muito orgulhoso com. Mas há alguns problemas legais não posso open-source agora e estou atualmente têm algum tempo livre. Então eu quero ver se consigo fazer outra VM em C (com maior velocidade) apenas por diversão e educacional.

A coisa é que eu não sou um programa C a última vez que escreveu um C problema não-trivial era mais do que 10 anos atrás. Eu estava Pascal, Delphi, e agora Java e PHP programador.

Há um número de obstáculos que podem prever e eu estou tentando resolver um e que está a aceder a biblioteca existente (em Java, reflexão resolve este problema).

I pretende resolver este por ter um tampão de dados (semelhante a pilha). O cliente do meu VM pode programar para colocar dados para estes pilha antes de me dar a ponteiro para função nativa.

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;
}

A empurrar, puxar e invocação da função nativa pode ser desencadeada por um código de byte (que é como VM, posteriormente, serão feitas).

Por uma questão de exaustividade (de modo que você pode experimentá-lo em sua máquina), aqui está o código para Stack:

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));
}

No lado função nativa, eu criei o seguinte para testar propósito:

// 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;

// Here is a helper function for displaying void PrintData(Data *pData, DDouble *pResult) { printf("%5.2f + %5.2f = %5.2f\n", pData->A*1.0, pData->B, pResult->D); }

// Some native function void Plus(char* pParams, char* pResult) { Data *D = (Data *)pParams; // Access data without arithmetic-pointer operation DDouble *DD = (DDouble *)pResult; // Same for return DD->D = D->A + D->B; PrintData(D, DD); }

Quando executado, o código acima retorna:

10.00 + 15.30 = 25.30
Result:  25.30
Pointer: 0

Este trabalho bem na minha máquina (Linux x86 32bits GCC-C99). Vai ser muito bom se isso funciona em outros OS / Architecture também. Mas há pelo menos três issures de memória relacionada com a que temos de ter em conta.

1). tamanho dos dados -. Parece que se eu compilar ambas as funções VM e nativos usando o mesmo compilador na mesma arquitetura, os tipos de tamanho deve ser o mesmo

2). Endianness -. Mesmo com o tamanho de dados

3). alinhamento de memória -. Qual é o problema como padding-bytes podem ser adicionados em struct, mas é difícil para sincronizá-lo quando preparar pilha parâmetro como (não há nenhuma maneira de saber como estofamento é adicionado, exceto para a codificação de disco rígido)

As minhas perguntas são:

1). Se eu sei o tamanho dos tipos, há uma maneira de modificar empurrar e puxar função exatamente para sincronizar com estofamento struct? (Modificar a deixar compilador cuida dele como problemas datasize e Endians).

2). Se eu embalar estrutura por um (usando #pragma pack(1)); (2.1) Será que o desempenho pena ser aceitável? e (2.2) Será que o programa de estabilidade estar em risco?

3). Como cerca de preenchimento de 2,4 ou 8? Que deve ser bom para a General sistema de 32 ou 64 bits?

4). você pode orientar-me a uma documentação para um algoritmo de preenchimento exato vamos dizer para o GCC no x86?

5). Existe uma maneira melhor?

NOTA: Cross-plataforma não é o meu objetivo final, mas eu não posso resistir. Além disso, o desempenho não é o meu alvo assim que não é tão feio. Todos estes são para diversão e aprendizagem.

Sorry for my Inglês e a muito longo post.

Obrigado a todos com antecedência.

Foi útil?

Solução

tangencial Comentários

Estes primeiros itens são tangenciais para as perguntas que você fez, mas ...

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

Eu acho que você provavelmente deve estar usando '* vazio' em vez de 'char *' aqui. Também gostaria de ter um typedef para o tipo de ponteiro de função:

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

Em seguida, você pode escrever:

Operator NativeFunction = Plus;

A função real seria modificado também - mas apenas muito ligeiramente:

void Plus(void *pParams, void *pResult)

Além disso, você tem um problema menor nomeação - esta função é 'IntPlusDoubleGivesDouble ()', ao invés de um propósito geral 'acrescentar quaisquer dois tipos' função

.

respostas diretas às perguntas

1). Se eu sei o tamanho dos tipos, há uma maneira de modificar empurrar e puxar função exatamente para sincronizar com estofamento struct? (Modificar a deixar compilador cuida dele como problemas datasize e Endians).

Não há uma maneira fácil de fazer isso. Por exemplo, considere:

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

Em algumas arquitecturas (32 bits ou 64 bits SPARC, por exemplo), a estrutura do tipo 1 terão 'número' alinhados em um limite de 4 bytes, mas a estrutura do tipo 2 terá 'número' alinhados sobre um 8- byte fronteira (e pode ter uma 'long double' em um limite de 16 bytes). Sua estratégia de 'empurrar elementos individuais dos que renove o ponteiro da pilha por 1 depois de empurrar o valor 'byte' - assim que você deseja mover o ponteiro da pilha por 3 ou 7 antes de empurrar o 'número', se o ponteiro da pilha não estiver adequadamente alinhados. Parte de sua descrição VM serão os alinhamentos necessários para qualquer tipo; o código de impulso correspondente, será necessário para assegurar o alinhamento correcto antes de empurrar.

2). Se eu embalar estrutura por um (usando #pragma pack (1)); (2.1) Será que o desempenho pena ser aceitável? e (2.2) Será que o programa de estabilidade estar em risco?

Em x86 e x86_64 máquinas, se você embalar os dados, você vai incorrer em uma penalidade de desempenho para o acesso a dados desalinhados. Em máquinas tais como SPARC ou PowerPC (por Mecki ), você receberá um erro de bus ou algo semelhante vez - você deve acessar os dados no seu alinhamento adequado. Você pode poupar algum espaço de memória - a um custo de desempenho. Você faria melhor para garantir o desempenho (que aqui inclui 'funcionando corretamente, em vez de bater') ao custo marginal no espaço.

3). Como cerca de preenchimento de 2,4 ou 8? Que deve ser bom para a General sistema de 32 ou 64 bits?

No SPARC, você precisa para preencher um tipo básico N-byte para uma fronteira N-byte. Em x86, você terá melhor desempenho se você fazer o mesmo.

4). você pode me guiar para uma documentação para dizer um preenchimento exato algoritmo do let para GCC no x86?

Você tem que ler o manual do .

5). Existe uma maneira melhor?

Note que o truque do 'Tipo 1' com um único caractere seguida por um tipo dá-lhe a exigência de alinhamento - possivelmente usando o 'offsetof) (' macro de <stddef.h>:

offsetof(struct Type1, number)

Bem, eu não iria embalar os dados na pilha - Gostaria de trabalhar com o alinhamento nativa porque isso está definido para dar a melhor performance. O escritor compilador não à toa adicionar preenchimento para uma estrutura; eles colocá-lo lá porque ele funciona 'melhor' para a arquitetura. Se você decidir que sabe melhor, você pode esperar as conseqüências habituais -. Programas mais lento que às vezes falham e não são tão portátil

Estou também não está convencido de que eu iria escrever o código nas funções de operador de assumir que a pilha contida uma estrutura. Eu iria puxar os valores do stack via o argumento Params, sabendo o que os deslocamentos e tipos corretos foram. Se eu empurrei um inteiro e um duplo, então eu puxar um inteiro e um duplo (ou, podeser, em ordem inversa - Eu puxar um casal e um int). A menos que você está planejando um VM incomum, algumas funções terá muitos argumentos.

Outras dicas

Interessante post e mostra que você colocou em um monte de trabalho. Quase o ideal SO postar.

Eu não tenho respostas prontas, por isso, tenha comigo. Vou ter que pedir mais algumas perguntas: P

1). Se eu sei o tamanho dos tipos, há uma maneira de modificar empurrar e puxar função exatamente para sincronizar com estofamento struct? (Modificar a deixar compilador cuida dele como problemas datasize e Endians).

É este a partir de um ponto de vista única performance? Você está planejando para introduzir ponteiros juntamente com tipos aritméticos nativas?

2). Se eu embalar estrutura por um (usando #pragma pack (1)); (2.1) Será que o desempenho pena ser aceitável? e (2.2) Será que o programa de estabilidade estar em risco?

Esta é uma coisa definida pela implementação. Não é algo que você pode contar com várias plataformas.

3). Como cerca de preenchimento de 2,4 ou 8? Que deve ser bom para a General sistema de 32 ou 64 bits?

O valor que combina com o tamanho da palavra nativa deve dar-lhe o melhor desempenho.

4). você pode orientar-me a uma documentação para um algoritmo de preenchimento exato vamos dizer para o GCC no x86?

Eu não sei de qualquer um em cima da minha cabeça. Mas eu vi um código semelhante ao este sendo usado.

Note que você pode especificar atributos de variáveis ?? usando GCC (que também tem algo chamado default_struct __attribute__((packed)) que desliga estofamento).

Existem algumas perguntas muito boas aqui, muitos deles vão ficar presos em algumas questões de design importantes, mas para a maioria de nós - podemos ver o que você está trabalhando para (dirkgently só postou como eu escrevo assim você pode ver que você está gerando juros), podemos compreender o seu Inglês muito bem que o que você está trabalhando no sentido de se algumas questões do compilador e alguns problemas de design de linguagem - torna-se difícil trabalhar a questão, mas em que você já está trabalhando em JNI há esperança ...

Por um lado, gostaria de tentar ficar longe de pragmas; Muitas pessoas, muito muitos vai discordar disso. Para a discussão canônica por ver a justificativa para a posição de linguagem D sobre o assunto. Por outro lado, existe um ponteiro de 16 bits enterrado em seu código.

As questões são quase infinitas, bem estudado, e é provável que se nós enterrados em oposição e intransigência intramural. se me permite sugerir a leitura Kenneth do Louden , bem como o manual de arquitetura Intel. Eu tenho, eu tentei lê-lo. alinhamento estrutura de dados, juntamente com muitos dos outros problemas que você posta à discussão estão profundamente enterradas na ciência compilador histórico e são susceptíveis de obter-lhe inundado de quem sabe o que. (Gíria ou idiomática para assuntos imprevisíveis de conseqüência)

Com isso dito, aqui vai:

  1. C-type tamanhos tamanhos que tipo?
  2. Computer Engineer antes de se move para Engenheiro de Software Já estudou microcontroladores? Hava uma olhada em alguns dos trabalhos de Don Lancaster.
  3. Pascal, Delphi, e agora Java e PHP programador. Aqueles são comparativamente removido da arquitectura fundamental de base de processadores, embora muitas pessoas irão mostrar ou tentar mostrar como eles podem ser usados ??para escrever rotinas poderosas e fundamentais. Sugiro olhando para analisador descendente recursivo de David Eck para ver exatamente como começar estudo da matéria. Como assim, Kenneth Louden tem uma implementação de "Tiny", que é um compilador real. Eu encontrei algo não muito tempo atrás que eu acho que foi chamado asm dot org ... muito avançado trabalho, muito poderoso estava disponível para estudo lá, mas é uma longa caminhada para começar a escrever em assembler com a intenção de entrar em ciência compilador. Além disso, a maioria das arquiteturas têm diferenças que não são consistentes de um processador para outro.
  4. acesso existente biblioteca

Existem muitas bibliotecas ao redor, Java tem alguns bons. Eu não sei sobre os outros. Uma abordagem é tentar escrever uma lib. Java tem uma sala de base boa e folhas para as pessoas gostam de tentar chegar a algo melhor. Comece com a melhoria Knuth-Morris-Pratt ou algo: Simplesmente não há escassez de lugares para começar. Tente Computer Programming Diretório Algoritmos e com certeza, olhar para Dicionário de algoritmos e Estruturas de dados no NIST

  1. always_inline

Não necessariamente, ver Dov Bulka - o trabalhador tem um Doutorado em CS e também é um autor proficiente em áreas onde o tempo-eficiência / confiabilidade-robustez e assim por diante não estão sujeitas a alguns dos "modelo de negócio" paradigma wherefrom temos alguns dos "Oh! isso não importa" em questões que realmente importam.

Como uma nota de fechamento, instrumentação e compreendem o controle sobre 60% do mercado real para habilidades de programação talentosos como você descreve. Por alguma razão, ouvimos principalmente sobre o modelo de negócio. Deixe-me compartilhar com você e dentro petisco eu tenho de uma fonte confiável. De 10% a 60% ou mais segurança real e risco de propriedade vem de questões veiculares que vem do assaltante, roubo e esse tipo de coisa. Você nunca vai ouvir apelos para "90 d! Ays Bustin minerais no faciltiy extração mineral concelho" para multas de trânsito, na verdade a maioria das pessoas nem sequer percebem multas de trânsito são (N. A. - EUA) classe 4 contravenção e são realmente classificável como tal

.

Parece-me que você tenha tomado um bom passo para um bom trabalho, ...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top