paulo1205
(usa Ubuntu)
Enviado em 11/11/2018 - 16:45h
Prezado Steve,
Começando pelo título, a rigor não faz sentido falar de “C puro” para tratar diretórios, pois o padrão do C não especifica funções voltadas para essa finalidade. O que você usou no seu programa é uma função do padrão POSIX, que usa o padrão do C e de sua biblioteca padrão, mas o estende significativamente com seu próprio conjunto de funções, variáveis e tipos de dados.
Steve escreveu:
1) Entendi que a função scandir ou alphasort não se deve mais usar. Então qual eu devo usar HOJE?
Ambas são parte do padrão POSIX, então ambas devem ser suportadas.
O problema é que, dependendo de como o seu compilador seja invocado, ele pode ligar ou desligar o suporte a algumas funções que sejam definidas por padrões que podem diminuir a compatibilidade do código.
Em várias páginas da documentação
on-line (
manpages ) do Linux sobre funções da biblioteca, existe uma seção que indica quais
feature_test_macros (ou seja: macros que o preprocessador testa a fim de habilitar determinados recursos) são necessárias para que as declarações dessas funções sejam efetivamente enxergadas pelo programa. Existe até uma
manpage que descreve todas as
feature_test_macros suportadas pela GNU libc.
Algumas dessas macros são afetadas por opções do compilador e por testes e definições/redefinições/indefinições presentes no cabeçalho <
features.h
>. Seria interessante saber como você fez para compilar seu programa.
2) Não compreendi o porque de 2 ** na frente de namelist, ainda estou tentando entender ponteiros, muito confuso pra mim...
Entender bem ponteiros é fundamental para se poder programar efetivamente em C.
Explicar ponteiros aqui com todo o cuidado necessário seria muito complicado. Resumindo de modo quase absurdo, um ponteiro é um
dado que representa um endereço de memória.
No caso da linguagem C (e também de C++), ponteiros possuem tipos de dados próprios: é possível saber que uma variável é um ponteiro desde o momento de sua declaração, e existem operações que só podem ser efetuadas com ponteiros ou só produzem resultados que sejam ponteiros. Além disso, cada ponteiro carrega, além do endereço que designa, informação sobre o tipo de dado que pode estar contido no endereço designado. Um ponteiro válido para dados inteiros implica um endereço de um valor inteiro; um ponteiro válido para dados do tipo
double implica um endereço de um valor do tipo
double ; um ponteiro válido para dados do tipo
struct tm implica um endereço de um valor do tipo
struct tm ; e assim por diante.
No meio desse “assim por diante” existem os casos de se poder ter ponteiros para dados cujos tipos são, eles próprios, também ponteiros. Você pode, portanto, ter um ponteiro para dado do tipo ponteiro para dado do tipo inteiro (“
int ** ”), ou ponteiro para dado do tipo ponteiro para dado do tipo ponteiro para dado do tipo
char (“
char *** ”).
O uso frequente de ponteiros em C se deve principalmente a duas situações: alocação dinâmica de memória e passagem de argumentos por referência.
Alocação dinâmica é relativamente simples: durante a execução do programa (e não durante a compilação), pede-se que o sistema reserve espaço na memória para uma determinada quantidade de dados contíguos, e o sistema entrega o endereço da primeira posição dessa memória (o qual, sendo endereço, requer uma variável de tipo ponteiro para ser guardado). Quando essa área de dados não for mais necessária, ela pode ser liberada e devolvida ao sistema (é justamente o propósito de
free (), sobre o qual você pergunta abaixo).
Já a passagem de parâmetros para funções em C tem a característica de ser sempre feita por meio de
cópias dos valores dos argumentos. Assim, por exemplo, quando se chama uma função como “
div(a, b) ”, a função
div () recebe uma cópia do valor da variável
a e uma cópia do valor da variável
b . Isso tem algumas implicações interessantes, entre as quais se destacam as seguintes: se o dado do argumento for muito grande (por exemplo, uma estrutura complexa ou contendo vetores com algumas dezenas ou mesmo centenas de
bytes ), a cópia do seu valor como argumento da função pode ser uma operação dispendiosa; além disso, se a função vier a alterar internamente um dos valores recebidos dos argumentos copiados, os valores originais fora da função serão preservados.
O emprego de ponteiros pode ajudar a contornar o impacto dessa característica de cópia de valores. Quando se passa à função um endereço, por meio de um ponteiro, a quantidade de
bytes a ser copiada para ser usada como parâmetro é de apenas o tamanho de um ponteiro (tipicamente 4 ou 8 bytes), independentemente do tamanho ocupado pelo dado apontado. Além disso, de posse do endereço de um objeto, a função pode usar esse endereço para chegar ao dado original, seja para consultá-lo, seja até mesmo para poder alterá-lo.
Para obter o endereço de um dado, tal dado tem de estar armazenado em uma variável (i.e. não pode ser um valor temporário), de modo a se poder obter o endereço de onde a variável está armazenada por meio do operador unário
& .
No caso de ponteiros para dados que podem ser alterados, uma das formas de alterá-los, usada principalmente com argumentos que são ponteiros para ponteiros, é justamente alocar memória nova e atribuir o endereço alocado ao ponteiro para ponteiro.
A função
scandir () recebe como segundo parâmetro um valor do tipo
struct dirent *** , ou seja: um “ponteiro para dado do tipo ponteiro para dado do tipo ponteiro para dado do tipo
struct dirent ”. E por que isso? Porque a ideia da função é que ela aloque dinamicamente (ponteiro!) um vetor de elementos do tipo
struct dirent que também são individualmente alocados dinamicamente (ponteiros!), e que ainda possa gravar esse dado no argumento recebido (ponteiro!).
Para que isso seja possível você tem de declarar uma variável compatível: ela tem de poder representar o vetor dinamicamente alocado (ponteiro) para elementos dinamicamente alocados (ponteiros) de dados do tipo
struct dirent , resultando numa declaração do tipo “
struct durent ** ”, e tem de passar o endereço, e não o valor, dessa variável para a função, a fim de que ela possa ser modificada dentro da função, que a razão pela qual você passa “
&namelist ” em vez de apenas “
namelist ”.
3) Eu queria saber o que é esse free e porque ele precisaria ser usado?
Para devolver ao sistema memória que tenha sido alocada dinamicamente.
Como não apenas o vetor, mas também cada um de seus elementos é alocado dinamicamente, quando você não precisar mais do resultado da função, você tem de liberar toda a memória alocada. Isso significa que você tem primeiro de liberar cada elemento, e depois liberar também o vetor. Nesse aspecto, o código que você mostrou está totalmente correto.
O problema de não liberar a memória é que, especialmente em programas que permanecem em execução durante muito tempo, toda memória que não for liberada vai se acumulando, potencialmente sem uso, e aos poucos exaurindo recursos do sistema, podendo chegar ao ponto de faltar recurso para alguma aplicação mais tarde.
(Em sistemas operacionais modernos, quando um programa termina, os recursos que ele alocou são tipicamente devolvidos automaticamente ao sistema e, por isso, para programas de execução rápida, nem sempre os programadores se preocupam com a devolução explícita de tais recursos. Isso, contudo, é uma faca de dois gumes: quem tem o hábito de não se preocupar com recursos em programas pequenos tem maior tendência de se esquecer de cuidar deles em programas de maior porte e de execução mais longa. Além do mais, nem sempre é verdade que os recursos alocados serão devolvidos ao sistema ao final da execução do programa — mesmo memória, em certos casos (como memória compartilhada) pode permanecer em uso até que seja explicitamente devolvida.)
4) Claro que neste código está faltando implementar um filtro para não listar arquivos e nem OCULTOS... Mas entendi que preciso colocar isso no while para filtrar como eu preciso. Pensei em usar um IF para isso...
Poderia, mas você poderia fazer através da própria
scandir (), alterando o terceiro argumento dela para um ponteiro de função que selecione quais nomes você quer colocar na lista retornada.
O terceiro e quarto argumentos são ponteiros para funções que a própria
scandir () vai chamar antes de lhe entregar o resultado da varredura do diretório. O terceiro argumento deve ser uma função que avalia se o nome deve ou não entrar na lista, e o quarto argumento é usado para reordenar a lista de entradas encontradas pela pesquisa antes de lha entregar.
Se o terceiro argumento for nulo, a função assume que todos os nós encontrados no diretório devem ser postos na lista. Contudo, você pode definir uma função que receba um ponteiro constante para dado do tipo
struct dirent , e avalie, usando os campos dessa estrutura, se você quer incluir os dados na lista ou não.
Como você falou que tem interesse apenas nos diretórios, talvez possa usar algo como o que vai abaixo.
int select_dirs(const struct dirent *entry){
// Retorna verdadeiro se for diretório e não for nem “.” nem “..”.
return
entry->d_type==DT_DIR &&
strcmp(entry->d_name, ".")!=0 &&
strcmp(entry->d_name, "..")!=0
;
}
(Com a ressalve de que nem todo sistema possui ou preenche corretamente o valor do campo
d_type , e mesmo sistemas que a entendem podem estar sujeitos a tipos de sistemas de arquivo que não dispõem da informação Pode ser, portanto, que seu filtro tenha de ser um pouco mais elaborado.)
5) Ainda não sei como ao invés de um PRINT eu enviaria para uma variável essa lista, e pensei em uma variável do tipo assim: Diretorios = pasta1, pasta2, pasta3, pasta4.....
A lista já estará pronta quando a função retornar. Se você se refere à operação de filtrar seus elementos, o melhor a fazer seria mesmo implementar um filtro e passá-lo à própria
scandir (), para que a lista devolvida já saia na forma que você desejar. Caso contrário, você acabará tendo de duplicar parte do esforço já feito e da memória já usada.