Recriei algumas das funções da biblioteca string.h [RESOLVIDO]

1. Recriei algumas das funções da biblioteca string.h [RESOLVIDO]

Perfil removido
removido

(usa Nenhuma)

Enviado em 23/04/2021 - 21:46h

Recentemente, seguindo um exercício propostos por uma apostila online desenvolvi a minha própria biblioteca de manipação de strings: a mystring.h. Eu não sei se ela ficou "100%", portanto eu gostaria que vocês me ajudassem tirando algumas das minhas dúvidas e sugerindo soluções mais eficientes para a minha biblioteca.

Segue o código:

Aquivo: mystring.h

//10/04/2021 - ZXKN-60R

#ifndef MYSTRING_H
#define MYSTRING_H

#include <stdlib.h>

//Funções de exame de string

extern size_t my_strlen(const char *s);
extern int my_strcmp(const char *s1, const char *s2);
extern int my_strncmp(const char *s1, const char *s2, size_t n);
extern const char *my_strchr(const char *s, int c);
extern const char *my_strrchr(const char *s, int c);
extern size_t my_strspn(const char *s, const char *accept);
extern size_t my_strcspn(const char *s, const char *reject);
extern const char *my_strpbrk(const char *s, const char *accept);
extern const char *my_strstr(const char *haystack, const char *needle);

//funções de manipulação de strings

extern char *my_strcpy(char *dest, const char *src);
extern char *my_strncpy(char *dest, const char *src, size_t n);
extern char *my_strcat(char *dest, const char *src);
extern char *my_strncat(char *dest, const char *src, size_t n);
extern void *my_memset(void *s, int c, size_t n);
extern void *my_memcpy(void *dest, const void *src, size_t n);
extern int my_memcmp(const void *s1, const void *s2, size_t n);
extern const void *my_memchr(const void *s, int c, size_t n);
extern const void *my_memrchr(const void *s, int c, size_t n); //não oficial da string.h

#endif //MYSTRING_H


Arquivo:mystring.c


#include "mystring.h"

//Funções de exame de string

size_t my_strlen(const char *s){

size_t lenght=0;

while(s[lenght]!='\0'){

lenght++;
}

return lenght;
}

int my_strcmp(const char *s1, const char *s2){

int diff=0;

for(unsigned int i=0; i<=my_strlen(s1); i++){

if(s1[i]!=s2[i]){

diff=(int)s1[i]-(int)s2[i];

break;
}
}

return diff;
}

int my_strncmp(const char *s1, const char *s2, size_t n){

int diff=0;

for(unsigned int i=0; i<=n; i++){

if(s1[i]!=s2[i]){

diff=(int)s1[i]-(int)s2[i];

break;
}
}

return diff;
}

const char *my_strchr(const char *s, int c){

while((*s!=(char)c) && (*s!='\0')){

s++;
}

if(*s!=(char)c){

s=NULL;
}

return s;
}

const char *my_strrchr(const char *s, int c){

size_t i=0;

for(size_t j=0; j<my_strlen(s); j++){

if(s[j]==(char)c){

i=j;
}
}

if(s[i]==(char)c){

s+=i;

}else{

s=NULL;
}

return s;
}

size_t my_strspn(const char *s, const char *accept){

size_t count=0;

for(size_t i=0; i<my_strlen(s); i++){

if(s[i]==accept[i]){

count++;

}else{

break;
}
}

return count;
}

size_t my_strcspn(const char *s, const char *reject){

size_t count=0;

for(size_t i=0; i<my_strlen(s); i++){

if(s[i]!=reject[i]){

count++;

}else{

break;
}
}

return count;
}

const char *my_strpbrk(const char *s, const char *accept){

int catch=0;

for(size_t i=0; i<my_strlen(s); i++){

for(size_t j=0; j<my_strlen(accept); j++){

if(s[i]==accept[j]){

s+=i;
catch++;

break;
}
}

if(catch!=0){

break;
}
}

if(catch==0){

s=NULL;
}

return s;
}

static int strstrcmp(size_t start, const char *haystack, const char *needle){

int diff=0;

haystack+=start;

while((*haystack!='\0') && (*needle!='\0')){

if(*haystack!=*needle){

diff=(int)*haystack-(int)*needle;

break;

}else{

haystack++;
needle++;
}
}

return diff;
}

const char *my_strstr(const char *haystack, const char *needle){

int catch=0;

size_t i, len=my_strlen(haystack);

for(i=0; i<len; i++){

if((haystack[i]==needle[0]) && (haystack[i+1]==needle[1])){

if(strstrcmp(i, haystack, needle)==0){

catch=1;

break;
}
}
}

if(catch==0){

for(i=0; i<len; i++){

if(strstrcmp(i, haystack, needle)==0){

catch=1;

break;
}
}
}

if(catch==0){

haystack=NULL;

}else{

haystack+=i;
}

return haystack;
}

//funções de manipulação de strings

char *my_strcpy(char *dest, const char *src){

size_t i=0;

while(src[i]!='\0'){

dest[i]=src[i];

i++;
}

dest[i]='\0';

return dest;
}

char *my_strncpy(char *dest, const char *src, size_t n){

size_t i=0;

while(i!=n){

dest[i]=src[i];

i++;
}

return dest;
}

char *my_strcat(char *dest, const char *src){

size_t i, len=my_strlen(dest);

for(i=0; (i<my_strlen(src)) && (src[i]!='\0'); i++){

dest[i+len]=src[i];
}

dest[i+len]='\0';

return dest;
}

char *my_strncat(char *dest, const char *src, size_t n){

size_t i, len=my_strlen(dest);

for(i=0; (i<n) && (src[i]!='\0'); i++){

dest[i+len]=src[i];
}

dest[i+len]='\0';

return dest;
}

void *my_memset(void *s, int c, size_t n){

unsigned char *ps=s;

for(size_t i=0; i<n; i++){

ps[i]=c;
}

return s;
}

void *my_memcpy(void *dest, const void *src, size_t n){

unsigned char *p_dest=dest;
const unsigned char *p_src=src;

for(size_t i=0; i<n; i++){

p_dest[i]=p_src[i];
}

return dest;
}

int my_memcmp(const void *s1, const void *s2, size_t n){

int diff=0;

const unsigned char *ps1=s1, *ps2=s2;

for(size_t i=0; i<n; i++){

if(ps1[i]!=ps2[i]){

diff=(int)ps1[i]-(int)ps2[i];

break;
}
}

return diff;
}

const void *my_memchr(const void *s, int c, size_t n){

const unsigned char *ps=s, uc=(unsigned char)c;

size_t i=0;

while((*ps!=uc) && (*ps!='\0') && (i<n)){

ps++;
i++;
}

if(*ps!=uc){

ps=NULL;
}

return ps;
}

const void *my_memrchr(const void *s, int c, size_t n){

const unsigned char *ps=s;

size_t i=0;

for(size_t j=0; j<n; j++){

if(ps[j]==(unsigned char)c){

i=j;
}
}

if(ps[i]==(unsigned char)c){

ps+=i;

}else{

ps=NULL;
}

return ps;
}


Minhas dúvidas:

1. O uso de casting de char para int (ou vice-versa) é desnecessário?

Eu utilizei casting de char para int dentro de algumas funções, mas lembrei que talvez isto seja desnecessário pelo fato de um int de 2 ou 4 bytes ser totalmente capaz de armazenar um char de meros 1 byte. Mas agora fica a dúvida: e se fosse o inverso? Faria diferença o casting de int para char?

2. unsigned char

De acordo com as páginas de manuais que eu encrontrei por aí, as funções memset, memcmp, memchr e memrchr interpretam os valores passadas para *s/*s1/*s2 como unsigned char:



void *memset(void *s, int c, size_t n); //prótotipo da função de acordo com as páginas de manuais do Linux

Sets the first num bytes of the block of memory pointed by ptr to the specified value (interpreted as an unsigned char). //Descrição da memset de acordo com o site cpluscplus.com

...

int memcmp(const void *s1, const void *s2, size_t n); //prótotipo da função de acordo com as páginas de manuais do Linux

The memcmp() function compares the first n bytes (each interpreted as unsigned char) of the memory areas s1 and s2. //Descrição da memcmp de acordo com o comando man memcmp

...

void *memchr(const void *s, int c, size_t n); //prótotipo da função de acordo com as páginas de manuais do Linux

void *memrchr(const void *s, int c, size_t n); //prótotipo da função de acordo com as páginas de manuais do Linux

The memchr() function scans the initial n bytes of the memory area
pointed to by s for the first instance of c. Both c and the bytes of
the memory area pointed to by s are interpreted as unsigned char.

The memrchr() function is like the memchr() function, except that it
searches backward from the end of the n bytes pointed to by s instead
of forward from the beginning.

//Descrição das funções memchr e memrchr de acordo com o comando man memchr


- https://www.cplusplus.com/reference/cstring/memset/
- https://linux.die.net/man/3/memcmp
- https://linux.die.net/man/3/memchr
- https://linux.die.net/man/3/memrchr


Dúvias em relação a este comportamento:

-> Por que em especial unsigned char e não um char assinado (signed char) ou int?

-> As função memcpy também segue esse mesmo modelo de trabalhar com unsigned char em seu funcionamento interno? (Eu não vi em nenhuma página de manual citando que essa função trabalha com unsigned char como as outras que eu mostrei)

-> A forma como eu refiz essas funções em minha biblioteca estão fieis em relação ao funcionamento interno original dessas funções?

3. memcpy()

Se tem uma coisa que eu não entendi muito bem em ralação a função memcpy foi a tal "sobreposição" (overlap) descrita em todas as página de manuais que eu li por aí.


The memcpy() function copies n bytes from memory area src to memory
area dest. The memory areas must not overlap. Use memmove(3) if the
memory areas do overlap
.

-> Alguém poderia me explicar o que uma sobreposição com a função memcpy()? Se sim, poderia demonstrar um exemplo de código que "caí" nesse "problema" de sobreposição?

-> A minha versão da memcpy (my_memcpy) também está sujeita a essa tal de sobreposição? Se sim, como eu poderia reverter?

4. Tabulação: 4 espaçamentos vs. 8 espaçamentos


Tabs are 8 characters, and thus indentations are also 8 characters. There are heretic movements that try to
make indentations 4 (or even 2!) characters deep, and that is akin to trying to define the value of PI to be 3.

- https://www.kernel.org/doc/html/v4.10/process/coding-style.html

Eu vejo muita gente usando tabulação de 4 espaçamentos em códigos escritos em C por aí (inclusive eu). Fui tentar usar tabulação de 8 espaçamentos eu achei meio esquisito.

Eu sei que isto TALVEZ seja uma questão mesquinha e pessoal, mas eu queria mesmo assim pedir desculpas pelos 4 meros espaçamentos em todo o meu código. Eu vou tentar me acostumar com o padrão de 8 espaçamentos nos meu próximos programas/códigos.


  


2. MELHOR RESPOSTA

Paulo
paulo1205

(usa Ubuntu)

Enviado em 25/04/2021 - 23:52h

ZXKN-60R escreveu:

[Código que você mostrou removido por brevidade.]

Minhas dúvidas:

1. O uso de casting de char para int (ou vice-versa) é desnecessário?

Eu utilizei casting de char para int dentro de algumas funções, mas lembrei que talvez isto seja desnecessário pelo fato de um int de 2 ou 4 bytes ser totalmente capaz de armazenar um char de meros 1 byte. Mas agora fica a dúvida: e se fosse o inverso? Faria diferença o casting de int para char?


A promoção de char/signed char/unsigned char para int ocorre automaticamente em qualquer expressão, então você não precisa fazer conversão explícita quando calcular a diferença entre dois caracteres. Só não pode cometer o erro — e você realmente não o cometeu — de gravar o resultado numa variável que seja do tipo char/signed char/unsigned char.

Fiquei na dúvida de se, ao perguntar se faria (ênfase minha) diferença conversão de int para char, você se refere a algum outro ponto do programa, além dos quatro em que você já fez essa conversão. Refere-se?

Sua pergunta me obrigou a ler detalhadamente a respeito de algumas funções que você está tentando reimplementar, tais como strchr() e memchr(), e isso me ajudou a ter um novo olhar sobre algumas dessas funções. Eu estava tentado a opinar que, por conta da promoção automática para int na hora de avaliar expressões, a conversão explícita de int para char seria inócua, mas percebi que não é, porque essa conversão explícita pode provocar uma mudança do valor inteiro. E aí me vieram muito mais dúvidas e até alguma indignação a respeito dessas funções. Por exemplo, com relação a strchr() (e, por conseguinte, com sua versão também):

  • Se o caráter a ser buscado vai ser convertido para char na hora de varrer a string, por que ele é passado à função por um parâmetro do tipo int, em lugar de char?

        Aqui geralmente se fala sobre razões históricas. Eu até compreendo, mas não fico muito satisfeito.

  • Se várias funções do C (especialmente as declaradas em <ctype.h>, mas também outras, como fgetc() e ungetc()) limitam o escopo de caracteres àquilo que pode ser representado como unsigned char ou a EOF mais aquilo que pode ser representado como unsigned char, por que strchr() não segue a mesma convenção?

        Esta aqui meio que se contrapõe à questão anterior, pois se ali a ênfase era em deixar o comportamento da função como existe hoje mais evidente na sua assinatura, aqui se cogita mudar o comportamento atual para deixá-lo mais consistente com outras funções da biblioteca, permitindo, por exemplo, que o código abaixo, que não tem nenhum erro sintático nem incompatibilidade de tipos nem promoção automática de char ára int, não tenha mais um comportamento errado caso o fim dos dados de entrada seja atingido (para ficar mais divertido, imagine esse programa roda num sistema que empregue um conjunto de caracteres de oito bits, tal como ISO-8859-1 ou Windows CP-1252, no qual 'ÿ' vale -1 quando representado como char com sinal ou na qual EOF, que vale -1, é convertido para 255 quando é convertido para char sem sinal).
// Conta quantos caracteres digitados em sequência fazem parte do nome da cidade francesa.
unsigned counter=0;
while(strchr("L'Haÿ-les-Roses", fgetc(stdin)))
++counter;


  • Se a função retorna um ponteiro para dentro da string recebida caso encontre nela o caráter procurado, como pode o ponteiro devolvido ser do tipo ponteiro para caracteres não-constantes, se o parâmetro que recebe a string é um ponteiro para caracteres constantes?

        Tal anomalia faz com que o compilador aceite como válido um código como o seguinte, que é perceptivelmente absurdo e seguramente vai dar problema na hora da execução.
*strchr("Teste", 'T')='P';  // Tenta transformar a constante "Teste" em "Peste", e o compilador C aceita isso alegremente. 

        Neste ponto, pelo menos o C++ se sai um pouquinho melhor, ao definir duas versões separadas de std::strchr(): uma que recebe e retorna ponteiros para caracteres constantes, e outra que aceita ponteiros para caracteres não-constantes e retorna um ponteiro que permite modificar tais dados.

  • Também não ajuda nada:

      • Ter três tipos distintos para representar caracteres: char, signed char e unsigned char (para não mencionar outros tipos, destinados a representar conjuntos de caracteres que não cabem em oito bits ou têm regras especiais, tais como wchar_t, char16_t, char32_t ou char8_t, que são macros ou typedefs em C (por isso em itálicos, e não em negrito), embora sejam tipos nativos próprios em C++, com as respectivas palavras reservadas).

      • Que fique em aberto para cada implementação escolher se char tem a mesma representação interna de signed char ou de unsigned char.

      • Que o tipo de dados de constantes literais de caracteres (a forma 'x') seja int, em vez de char/signed char/unsigned char (em C++, essa decisão esdrúxula foi parcialmente corrigida: o tipo de 'x' é char, mas ainda não se sabe se esse char tem comportamento mais parecido com signed char ou unsigned char).

      • Que mesmo usando o tipo int para as constantes de caracteres, não se possa dizer inequivocamente apenas olhando o código fonte qual dos dois testes será verdadeiro: “'\377'==0377” e “'\377'==EOF”.

2. unsigned char

De acordo com as páginas de manuais que eu encrontrei por aí, as funções memset, memcmp, memchr e memrchr interpretam os valores passadas para *s/*s1/*s2 como unsigned char:

void *memset(void *s, int c, size_t n); //prótotipo da função de acordo com as páginas de manuais do Linux

Sets the first num bytes of the block of memory pointed by ptr to the specified value (interpreted as an unsigned char). //Descrição da memset de acordo com o site cpluscplus.com

...

int memcmp(const void *s1, const void *s2, size_t n); //prótotipo da função de acordo com as páginas de manuais do Linux

The memcmp() function compares the first n bytes (each interpreted as unsigned char) of the memory areas s1 and s2. //Descrição da memcmp de acordo com o comando man memcmp

...

void *memchr(const void *s, int c, size_t n); //prótotipo da função de acordo com as páginas de manuais do Linux

void *memrchr(const void *s, int c, size_t n); //prótotipo da função de acordo com as páginas de manuais do Linux

The memchr() function scans the initial n bytes of the memory area
pointed to by s for the first instance of c. Both c and the bytes of
the memory area pointed to by s are interpreted as unsigned char.

The memrchr() function is like the memchr() function, except that it
searches backward from the end of the n bytes pointed to by s instead
of forward from the beginning.

//Descrição das funções memchr e memrchr de acordo com o comando man memchr

- https://www.cplusplus.com/reference/cstring/memset/
- https://linux.die.net/man/3/memcmp
- https://linux.die.net/man/3/memchr
- https://linux.die.net/man/3/memrchr


Dúvias em relação a este comportamento:

-> Por que em especial unsigned char e não um char assinado (signed char) ou int?


Cuidado com a tradução, porque signed, no caso do qualificador que indica se o tipo de dados pode representar valores negativos, seria mais adequadamente traduzido como com sinal do que como assinado.

Note que essas funções usam como tipo dos ponteiros const void *, não const char *. Elas lidam com a memória bruta, não como strings, e por isso consideram que os dados nessa memória bruta são compostos por bytes, não necessariamente por caracteres de um determinado conjunto de símbolos. E a forma mais habitual de pensar em termos de bytes é considerar que seus valores vão de 0 a 255, sendo menos comum e geralmente restrita a contextos específicos pensar neles como indo de -128 a 127.

-> As função memcpy também segue esse mesmo modelo de trabalhar com unsigned char em seu funcionamento interno? (Eu não vi em nenhuma página de manual citando que essa função trabalha com unsigned char como as outras que eu mostrei)


Nesse caso não faz diferença. Você não vai comparar elementos da memória bruta com algum outro valor ou copiar um valor que você mesmo forneceu para memória bruta, mas sim copiar “na marra” dados de um tipo qualquer de um lugar da memória para outro. Qualuer tipo intermediário serve, desde que, no fim das contas, os blocos de memória fiquem idênticos.

-> A forma como eu refiz essas funções em minha biblioteca estão fieis em relação ao funcionamento interno original dessas funções?


Acho que as funções poderiam ser mais simples, mas as implementações me parecem atender a especificação das funções.

3. memcpy()

Se tem uma coisa que eu não entendi muito bem em ralação a função memcpy foi a tal "sobreposição" (overlap) descrita em todas as página de manuais que eu li por aí.

The  memcpy()  function  copies n bytes from memory area src to memory
area dest. The memory areas must not overlap. Use memmove(3) if the
memory areas do overlap
.

-> Alguém poderia me explicar o que uma sobreposição com a função memcpy()? Se sim, poderia demonstrar um exemplo de código que "caí" nesse "problema" de sobreposição?


Exemplo de sobreposição:
char str[]="Paulo";
char *p=str+1; // mesma coisa que “*p=&s[1]”.
memcpy(p, str, 4); // XXX: Não se sabe qual resultado isto aqui vai ter!


A implementação óbvia e simplista de memcpy() seria copiar os bytes em ordem crescente do endereço. Nesse caso, o código acima produziria como resultado o conteúdo "PPPPP" em str. Mas pode ser que alguém saiba que numa determinada plataforma é mais eficiente varrer os endereços de trás para frente, e poderia ficar tentado a ganhar desempenho implementando o algoritmo dessa maneira. Nesse caso, o conteúdo da string resultante em str nessa implementação poderia ser "PPaul".

Ao especificar que as duas regiões de memória não devem se sobrepor, o padrão abre espaço para que a implementação de memcpy() possa usar a implementação mais eficiente possível para a máquina, sem ter de levar em consideração possíveis efeitos colaterais de possíveis sobreposições entre o que ter de ser copiado e o destino da cópia. No entanto, essa responsabilidade passa para o usuário da função, que tem de estar ciente de que se ele desrespeitar a condição de não-sobreposição, pode ter resultados inconsistentes ou comportamento indefinido.

Se o programador souber que existe superposição ou se não tiver como prever se vai ou não ocorrer, ele deve usar outra função, tal como memmove(), que tem um comportamento sempre consistente e previsível, mas possivelmente com menor desempenho.

-> A minha versão da memcpy (my_memcpy) também está sujeita a essa tal de sobreposição? Se sim, como eu poderia reverter?


Na verdade, como eu disse acima, a documentação fala em superposição para que você, como implementador, não precise se preocupar com ela, tornando-a problema do usuário.

4. Tabulação: 4 espaçamentos vs. 8 espaçamentos

Tabs are 8 characters, and thus indentations are also 8 characters. There are heretic movements that try to
make indentations 4 (or even 2!) characters deep, and that is akin to trying to define the value of PI to be 3.

- https://www.kernel.org/doc/html/v4.10/process/coding-style.html

Eu vejo muita gente usando tabulação de 4 espaçamentos em códigos escritos em C por aí (inclusive eu). Fui tentar usar tabulação de 8 espaçamentos eu achei meio esquisito.

Eu sei que isto TALVEZ seja uma questão mesquinha e pessoal, mas eu queria mesmo assim pedir desculpas pelos 4 meros espaçamentos em todo o meu código. Eu vou tentar me acostumar com o padrão de 8 espaçamentos nos meu próximos programas/códigos.


Aqui vai muito a questão de gosto. Mas, no caso da afirmação que você citou acima, vai também um bocado de arrogância de quem acha que suas preferências pessoais são boas e que todos os que usam de outro modo são estúpidos ou inferiores — ou hereges —, uma generalização temerária e imprecisa (“tabulações são oito caracteres”, que é verdadeira em muito constextos, mas está longe de ser universal) e uma analogia ruim com tentar alterar o valor de uma constante que não vem de uma convenção (ou, na verdade, de muitas convenções diferentes em diferentes sistemas), mas da descoberta de uma propriedade da Matemática que é imutável em qualquer parte do nosso universo ou, aliás, independente até mesmo da existência de qualquer universo físico.

É uma pena que o criador do Linux seja famoso por esse tipo de falastronice. E é dele, por sinal, a que você citou.

Gosto por gosto, o que eu costumo fazer é usar sempre e exclusivamente tabulação, desabilitando no meu editor qualquer conversão automática de espaços para tabulações ou no sentido oposto. Quem abrir o meu código-fonte e quiser que a tabulação tenha a mesma largura que oito espaços, ou quatro, ou cinco, ou três ou dois, ou vinte e três e dois terços, que ajuste seu editor para tanto. Dá para todo mundo ficar feliz ao mesmo tempo e com o mesmo código.

A não ser que seja uma daquelas pessoas que insistem em usar editores de texto muito queixos-duros, que não têm como ajustar o tamanho da tabulação, ou um daqueles que acham errado usar tabulações flexíveis, e insistem que só uma quantidade fixa de espaços (que depende de quem estiver falando) é que sinaliza virtude. Pensem eles o que quiserem; se não pagam o meu salário, não devo lealdade ao seu masoquismo ou à sua teimosia.


... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)

3. Re: Recriei algumas das funções da biblioteca string.h [RESOLVIDO]

Mauricio Ferrari
maurixnovatrento

(usa Slackware)

Enviado em 24/04/2021 - 22:51h


Com certeza, terá uma pessoa com mais experiência do que eu e que te explicará isso.

Eu só estou passando para dizer que independente de qualquer coisa, é um ótimo trabalho.

___________________________________________________________
Conhecimento não se Leva para o Túmulo.
https://github.com/MauricioFerrari-NovaTrento



4. Re: Recriei algumas das funções da biblioteca string.h [RESOLVIDO]

berghetti
berghetti

(usa Debian)

Enviado em 25/04/2021 - 14:01h

Interessante
olha a implementação de strlen da glibc

https://code.woboq.org/userspace/glibc/string/strlen.c.html






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts