Ybadoo - Soluções em Software Livre
Turmas
2º Semestre de 2016

Desenvolva um programa em Simpletron Machine Language, que apresente a média dos números fornecidos pelo usuário, até que o usuário forneça o número zero. Por exemplo, caso os valores fornecidos pelo usuário sejam 2, 7, 4, 3 e 0, o programa deverá apresentar como resposta o valor 4, ou seja, (2 + 7 + 4 + 3) / 4.

Questão 02

A geração de código intermediário é a transformação da árvore de derivação em um segmento de código. Esse código pode, eventualmente, ser o código objeto final, mas, na maioria das vezes, constitui-se num código intermediário, pois a tradução de código fonte para objeto em mais de um passo apresenta algumas vantagens. Existem vários tipos de código intermediário, como por exemplo, as representações gráficas por meio de árvores e grafo de sintaxe. Uma árvore de sintaxe é uma forma condensada de árvore de derivação na qual somente os operandos da linguagem aparecem como folhas; os operadores constituem nós interiores da árvore. Outra simplificação da árvore de sintaxe é que cadeias de produções simples (por exemplo, A → B, B → C) são eliminadas. Um grafo de sintaxe, além de incluir as simplificações da árvore de sintaxe, faz a fatoração das subexpressões comuns, eliminando-as.

Conforme exposto, qual das expressões numéricas está representada pelo grafo de sintaxe apresentado a seguir, considerando a gramática livre de contexto G.

Grafo de sintaxe
Grafo de sintaxe
G = ({A, E, T, F, V}, {a, b, c, d, e, f, =, +, -, *, /, (, )}, P, A)
P = {AV=E
EE+T | E-T | T
TT*F | T/F | F
F → (E) | V
V → a | b | c | d | e | f}

a. a = (a * (b + c – b + c) / (d * e)) + ((b + c) / (d * e)) + (d * e * f)

b. a = (a * (b + c)) – ((b + c) / (d * e)) + ((b + c) / (d * e)) + (d * e * f)

c. a = (a * (b + c)) – ((b + c) / (d * e)) + ((d * e) * f)

d. a = ((a * (b + c)) – ((b + c) / (d * e))) + (((b + c) / (d * e)) + (d * e * f))

e. a = ((a * (b + c)) – ((b + c) / (d * e))) + ((b + c) / (d * e * f))

Questão 03

Se E1 e E2 são expressões pós-fixadas e q é um operador binário, então, E1 E2 q é a representação pós-fixada para a expressão E1 q E2. Se, por outro lado, E1 e E2 são expressões pré-fixadas, então q E1 E2 é a representação pré-fixada para a expressão E1 q E2. Notações pós e pré-fixadas podem ser generalizadas para operadores n-ários. Para a avaliação de expressões pós-fixadas, pode-se utilizar uma pilha e um processo que age do seguinte modo: lê a expressão da esquerda para a direita, empilhando cada operando até encontrar um operador. Encontrando um operador n-ário, aplica o operador aos n operandos do topo da pilha. Processamento semelhante pode ser aplicado para avaliação de expressões pré-fixadas; nesse caso, a expressão é lida da direita para a esquerda.

Conforme exposto, qual das alternativas é a correta para a expressão numérica a = a * b + c - d * e / f + g considerando a gramática livre de contexto G.

G = ({A, E, T, F, V}, {a, b, c, d, e, f, g, =, +, -, *, /, (, )}, P, A)
P = {AV=E
ET+E | T-E | T
TF*T | F/T | F
F → (E) | V
V → a | b | c | d | e | f | g}

a. a notação pós-fixada correspondente é a a b * c d e f / * g + - + =

b. a notação pré-fixada correspondente é = a + - + * a b c / * d e f g

c. a notação pós-fixada correspondente é a a b c d e f g + / * - + * =

d. a notação pré-fixada correspondente é = + - + * / * a a b c d e f g

e. a notação pós-fixada correspondente é a a b * c + d e * f / - g + =

A fase de otimização do código intermediário vem logo após a fase de geração desse código e tem por objetivo tornar o código intermediário mais apropriado para a produção de código objeto (código de máquina) eficiente, tanto em relação ao tamanho como ao tempo de execução. Uma das técnicas de otimização de código intermediário consiste em identificar segmentos sequencias do programa, chamados blocos básicos, e representá-los através de grafos dirigidos e submetê-los a um processo de otimização. Apresente o código de três endereços não otimizado (a), o grafo acíclico dirigido para blocos básicos (b) e o código de três endereços otimizado pela aplicação do método de construção do grafo acíclico dirigido (c), da seguinte sequência de comandos:

x = b * c * d;
y = a + b * c * d + e;
z = a + b * c * d + e + f;

sobre a gramática livre de contexto G4

G = ({A, E, T, F, V}, {a, b, c, d, e, f, x, y, z, =, +, -, *, /, (, )}, P, A)
P = {AV=E
EE+T | E-T | T
TT*F | T/F | F
F → (E) | V
V → a | b | c | d | e | f | x | y | z}

Questão 05

No programa abaixo, escrito na sintaxe do C, os parâmetros da função sub são passados por nome.

void sub(int i, int j, int k) {
i = i + j;
k = j + k;
}

void main() {
int x = 5;
int y = 8;

sub(x, x + y, y);

printf("%d", y);
}

O texto impresso na última linha do programa é:

a. 8

b. 16

c. 18

d. 21

e. 34

Questão extra

Os principais requisitos impostos a geradores de código objeto são os seguintes: a) o código gerado deve ser correto e de alta qualidade; b) o código deve fazer uso efetivo dos recursos da máquina e; c) o código gerado deve executar eficientemente. O problema de gerar código ótimo é insolúvel (indecidível) como tantos outros. Na prática, devemos contentar-nos com técnicas heurísticas que geram bom código (não necessariamente ótimo).

Conforme exposto, qual é o melhor código objeto apresentado para a instrução x = (a - b) / (b - c) / (c + d)

a.

LOAD   a, R0
LOAD b, R1
SUB R1, R0
LOAD c, R2
SUB R2, R1
DIV R0, R1
LOAD d, R1
ADD R1, R2
DIV R2, R0
STORE R0, x

b.

LOAD   a, R0
LOAD b, R1
SUB R1, R0
LOAD c, R2
SUB R1, R2
DIV R0, R1
LOAD d, R1
ADD R1, R2
DIV R2, R0
STORE R0, x

c.

LOAD   a, R0
LOAD b, R1
SUB R1, R0
LOAD c, R2
SUB R2, R1
DIV R1, R0
LOAD d, R1
ADD R1, R2
DIV R2, R0
STORE R0, x

d.

LOAD   a, R0
LOAD b, R1
SUB R0, R1
LOAD c, R2
SUB R2, R1
DIV R1, R0
LOAD d, R1
ADD R1, R2
DIV R2, R0
STORE R0, x

e.

LOAD   a, R0
LOAD b, R1
SUB R1, R0
LOAD c, R2
SUB R2, R1
DIV R1, R0
LOAD d, R1
ADD R1, R2
DIV R0, R2
STORE R2, x