Na linguagem de programação C é possível passar parâmetros de entrada num programa, utilizando o argc e o argv. A função main recebe dois parâmetros de entrada:
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("argc: %d\n", argc);
printf("argv: %s\n", argv[0]);
return 0;
}
argc
– valor inteiro que indica o número de argumentos que foram passados ao chamar o programa na linha de comandos;
argv
– array de apontadores para caracteres, que contém os argumentos, um para cada "palavra" passada na linha de comandos.
Como o argv[0] armazena sempre o nome do programa que é executado na linha de comandos, o valor do argc é no mínimo igual a 1, porque existe sempre um argumento de entrada, o nome do programa (argv[0]).
O número de argumentos passados à função é potencialmente infinito. No código seguinte enumeramos todos os argumentos enviados:
int main(int argc, char *argv[])
{
int i;
for (i = 0; i < argc; i++)
printf("%d Parâmetro: %s\n", i, argv[i]);
return 0;
}
Convém não esquecer que os argumentos são todos tratados inicialmente como strings. Para transformá-los, quando aplicável, em inteiros podemos usar a função da Standard Library atoi
, que retorna o valor do número inteiro ou zero em caso de erro de conversão:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if (argc != 3)
{
printf("erro: sintaxe invalida!");
return 1;
}
int num1, num2;
num1 = atoi(argv[1]);
num2 = atoi(argv[2]);
printf("resultado: %d\n", num1 * num2);
return 0;
}
O ficheiro arguments.c
contém um exemplo de código em C que multiplica dois números mediante os argumentos passados. É robusto quanto ao número de argumentos passados e ao tipo dos mesmos.
Para usá-lo basta no Minix3 correr os seguintes comandos:
minix$ cc -Wall arguments.c -o arguments
minix$ ./arguments <ARG1> <ARG2>
Note-se que para resolver o problema acima descrito, a condição seguinte para capturar um erro de conversão não está completamente correcta:
// entra na condição se pelo menos uma das conversões tiver dado 0 (zero)
if (!num1 || !num2) {
printf("erro: sintaxe dos argumentos inválida!\n");
return 1;
}
O problema está no facto de que pelo menos um dos argumentos pode ser mesmo 0 (zero), e nesse caso a operação de multiplicação deve ser realizada. Para contornar este caso podemos identificar se o argumento é originalmente uma string que contenha 0. Não podemos, no entanto, fazer uma comparação direta: lembro que as strings em C terminam todas com o caracter de terminação '\0' e a comparação entre strings e chars não é permitida. Uma solução para identificar se um argumento é um zero poderá ser a seguinte:
// "0" é igual a "0\0", mas strlen("0") == 1
if (argv[1] == '0') // errado
if (argv[1][0] == '0') // errado, pois "0fgh" deverá também dar erro
if (argv[1][0] == '0' && strlen(argv[1]) == 1) // certo
Assim, a condição final ficará:
if ((!num1 && !(argv[1][0] == '0' && strlen(argv[1]) == 1)) ||
(!num2 && !(argv[2][0] == '0' && strlen(argv[2]) == 1))) {
printf("erro: sintaxe dos argumentos inválida!\n");
return 1;
}
Ou de forma mais organizada:
int valid(const char *arg) {
return atoi(arg) || (arg[0] == '0' && strlen(arg) == 1);
}
if (!valid(argv[1]) || !valid(argv[2])) {
printf("erro: sintaxe dos argumentos inválida!\n");
return 1;
}
Em linguagem C, o tamanho de dados mínimo que se pode alterar é um byte (8 bits):
#include <stdio.h>
int main(int argc, char *argv[])
{
unsigned char c;
// definir o valor inicial de c
c = 95; // 0b01011111
printf("c: %d (%c)\n", c, c);
// alterar o valor de c
c = 100; // 0b01100100
printf("c: %d (%c)\n", c, c);
return 0;
}
Para alterar algum bit do valor original é necessário usar máscaras e operações de bitwise:
Importante para desativar um determinado bit
0011 1000
& 1010 1001
-------------
0010 1000
Importante para ativar um determinado bit
0011 1000
| 1010 1001
-------------
1011 1001
Importante para inverter um determinado bit
0011 1000
^ 1010 1001
-------------
1001 0001
O ficheiro masks.c
contém um exemplo de código em C que calcula valores usando estas três operações
Para usá-lo basta no Minix3 correr os seguintes comandos:
minix$ cc -Wall masks.c -o masks
minix$ ./masks
Estas operações de bitwise dão, por exemplo, para determinar se um número é par ou ímpar. Na sua versão tradicional o código podia ser implementado da seguinte maneira:
// Return 0 -> number is even
// Return 1 -> number is odd
int is_odd_v1(const char number) {
return number % 2;
}
Utilizando a operação AND é possível determinar se o número é ímpar simplesmente assim:
// Se number = 0x???????1 (ímpar), então 0x???????1 & 0x00000001 = 1
// Se number = 0x???????0 (par), então 0x???????0 & 0x00000001 = 0
int is_odd_v2(const char number) {
return number & 1;
}
De uma forma muito simples, as macros em linguagem C são basicamente substituições de strings. As Macros são tratadas (expandidas) pelo pré-processador e criadas em linguagem C através da diretiva #define. Normalmente, as macros são definidas em letras maiúsculas, desta forma é fácil identificar uma macro ao ler o código de um programa.
#define TRUE 1
#define FALSE 0
int is_number(char c)
{
if (c >= '0' && c <= '9')
return TRUE;
return FALSE;
}
Quando o código for compilado, o pré-processador substitui cada uma das macros pelo seu valor. Neste caso, onde aparece TRUE será substituído por 1, e onde aparece FALSE, será substituído por 0.
Em LCOM ao longo do semestre iremos usar macros ao implementar device drivers para o sistema operativo MINIX. A mais conhecida e importante é a BIT:
// BIT(0) = 0b00000001 = 1
// BIT(1) = 0b00000010 = 2
// BIT(2) = 0b00000100 = 4
// ...
#define BIT(N) (1 << (N))
O ficheiro macros.c
contém um exemplo de código em C que calcula valores usando a macro anterior.
Para usá-lo basta no Minix3 correr os seguintes comandos:
minix$ cc -Wall macros.c -o macros
minix$ ./macros
Tal como o nome sugere, os operadores de shifting / deslocamento efectuam o deslocamento de bits. Na linguagem C existem dois operadores de deslocamento:
- deslocamento à esquerda: valor << n, que é constituído por dois operandos (valor e n) e um operador <<;
- deslocamento à direita: valor >> n, que é constituído por dois operandos (valor e n) e um operador >>.
Caso o deslocamento fosse de n bits para a esquerda, perder-se-iam os n bits mais significativos, sendo ainda acrescentados n bits a 0 como bits menos significativos.
Quando efectuada sobre a representação binária de um número inteiro, a operação deslocamento para a esquerda em n bits produz um valor que corresponde à multiplicação por 2^n do valor inteiro original. Por exemplo, o deslocamento em um bit para a esquerda do valor original 00010010 que corresponde ao inteiro 18 em base decimal, é transformado no valor 00100100 que corresponde ao valor 36 em base decimal, isto é, ao dobro do valor original.
O operador deslocamento para a direita funciona de forma análoga ao operador de deslocamento para a esquerda, alterando-se somente o sentido do deslocamento. Assim, na operação de deslocamento à direita de n bits, há lugar à deslocação de n bits para a direita.
int is_bit_um(int valor, int num_bit)
{
int num_bits_int = sizeof(valor) * 8;
assert( num_bit < 32 );
int mascara_num_bit = (1 << num_bit);
return valor & mascara_num_bit;
}
#define BIT(n) (1 << (n))
int main(int argc, char *argv[])
{
char val;
int n;
printf("Insira o valor de val: ");
scanf("%c", &val);
printf("Insira o valor de n: ");
scanf("%d", &n);
if (n < 0 || n > 7) {
printf("error: valor de 'n' invalido [0..7]\n");
return 1;
}
val = val | BIT(n);
printf("O novo valor de 'val': 0x%8x\n", val);
return 0;
}
O ficheiro shift.c
contém um exemplo de código em C que ativa o bit N de um valor val.
Para usá-lo basta no Minix3 correr os seguintes comandos:
minix$ cc -Wall shift.c -o shift
minix$ ./shift <VAL> <N>
Escreva um programa em linguagem C, que recebe 3 argumentos na linha de comandos:
- O caracter que especifica a acção que se pretende realizar:
h
- testar se o valor de um determinado bit está a 1 (high)l
- testar se o valor de um determinado bit está a 0 (low)r
- colocar a 0 o valor de um determinado bit (reset)s
- colocar a 1 o valor de um determinado bit (set)t
- inverter o valor de um determinado bit (toggle)
- Um valor inteiro sem sinal, em hexadecimal, menor que 256;
- O índice do bit que se pretende aceder, de 0 a 7
O programa deve:
- a) Validar os argumentos de entrada e imprimir uma mensagem de erro apropriada, caso os valores não estejam correctos;
- b) Imprimir os argumentos depois de estes serem validados (o segundo argumento deve ser imprimido em binário usando a função byte2bin);
- c) Executar a acção pretendida (depois de imprimir o nome do programa);
- d) Imprimir os valores de saída da operação realizada (usar a função byte2bin).
Há parte do código que já é dado:
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#define BIT(n) (1<<(n))
char *byte2bin(uint8_t n, char *binstr)
{
// one element per bit (0/1)
uint8_t binary[8];
int i = 0;
for(i = 0; i < 8; i++) {
binary[i] = n % 2;
n = n / 2;
}
// printing binary array in reverse order
for (int j = 7, k= 0; j >= 0; j--, k++)
sprintf(binstr + k, "%d", binary[j]);
return binstr;
}
void print_usage(char *name) {
printf("Usage: %s <action> <byte> <bit no>\n", name);
printf("\tWhere: <action> one of 'h'|'l'|'r'|'s'|'t'\n"
"\t\t <byte> 8-bit integer in hexadecimal\n"
"\t\t <bit no> of bit to operate on\n");
}
Uma possível solução para o problema está implementada no ficheiro exercise.c.
@ Nuno Cardoso & Fábio Sá
@ Fevereiro de 2023