Ir para conteúdo
  • Cadastre-se
  • Entre para seguir isso  

    Especificações da linguagem C

       (0 análises)

    Fernando Mercês

    Há algum tempo tinha vontade de escrever algo sobre os padrões da linguagem C e recentemente, motivado por uma discussão na lista exploits-brasil [1] e aproveitando um domingo nublado no Rio de Janeiro, decidi começar.

    Este artigo vai ajudá-lo a entender (e quando utilizar) os padrões de programação da linguagem C. Não ache que a utilidade é teórica, pois não é. Seguir um padrão pode ajudar na segurança de código, otimização e até aumentar a produtividade, como veremos mais a diante (sabia que C tem o tipo boolean, por exemplo?).

    1. Introdução

    A maioria das linguagens de programação seguem padrões abertos, que os criadores de compiladores (ou interpretadores) precisam seguir para suportar a linguagem completamente. A linguagem C foi criada por Denis Ritchie (falecido em 2011) entre 1969 e 1973 com o objetivo de substituir o Assembly (o que explica a existência comandos como goto na linguagem). Desde então, várias versões de compiladores foram criadas, por várias pessoas e empresas, sem seguir uma especificação oficial. Por isso, em 1983, o American National Standards Institute, mais conhecido pela siga ANSI, formou um comitê para estabelecer um padrão, que só foi concluído em 1989.

    2. Linha do tempo da linguagem C

    • 1969-1973: Denis Ritchie cria a linguagem C
    • 1983: O ANSI cria um comitê para especificar a linguagem
    • 1989: A especificação ANSI X3.159-1989 é concluída. O nome informal dela é C89 e também é conhecida por ANSI C.
    • 1990: O ISO (International Organization for Standardization) assume a especificação de linguagem e cria um grupo de trabalho (working group) chamado WG14 [2] para cuidar da especificação do C, que é renomeada para ISO/IEC 9899:1990, informalmente conhecida como C90. Não houve mudanças na linguagem. Esta especificação se mantém por vários anos e define a linguagem C que “todos conhecem”.
    • 1999: O WG14 publica a especificação ISO/IEC 9899:1999, informalmente chamada de C99. Esta já traz várias novidades que serão tratadas mais à frente neste artigo.
    • 2011: O WG14 publica a ISO/IEC 9899:2011, conhecida como C11. Este é o padrão atual da linguagem.

    Vários recursos cobertos nas especificações mais recentes já eram implementados pelos desenvolvedores de compiladores por sua conta. No caso do GCC, por exemplo, o Projeto GNU criou os padrões GNU89, GNU99 e GNU11 com suas próprias extensões. O Visual C++ da Microsoft também tem várias extensões da linguagem (lembre-se que em geral qualquer compilador C++ compila C, pois a linguagem é a mesma, mas a recíproca não é verdadeira).

    Vou mostrar alguns recursos das especificações atuais, utilizando o gcc 4.6 para os exemplos.

    A opção -std do gcc define qual especificação usar. Já a -pedantic faz o gcc alertar sobre qualquer construção que seja proibida pela especificação, de forma bem rigorosa.

    3. Recursos da C99

    • Tipo booleano:

    Incluindo a stdbool.h, você pode declarar variáveis do tipo bool e usar os estados verdadeiro e falso, assim:

    #include <stdbool.h>
    
    void main()
    {
    	bool var;
      
    	if (var)
    		var = false;
    }

    É fato que isso já era feito usando-se 1 e 0 e na verdade os estados true e false não passam de outros nomes (#define) para estes números, respectivamente. No entanto, a legibilidade do código melhora com o tipo bool.

    • Comentários com //

    A C89 só permite comentários entre /* e */, o que dá um trabalho extra para comentários de uma linha. Usando a C99, você pode iniciar a linha com //, como no C++, PHP etc. e esta linha será um comentário perfeitamente válido.

    • Funções em linha

    Quando você chama uma função no código, o compilador normalmente gera uma instrução CALL (do Assembly) para desviar o fluxo de código para o endereço da função. Isso envolve passagem de parâmetros pela pilha de memória (stack), retorno etc. Ou seja, isto gera um ônus de processamento e consumo de memória considerável, principalmente associado ao número e tamanho dos argumentos. Um código que não use funções é mais otimizado, no entanto, é mais difícil de manter. Para resolver isso, você pode dizer que uma função é em linha (inline). Dessa forma, toda vez que a função for chamada, o código dela será copiado para o trecho onde a chamada aconteceu. É como se você copiasse e colasse a função em vários trechos, mas sem precisar fazer isso.;)

    Imagine que temos a função:

    inline int soma(int num1, num2)
    {
    	return num1 + num2;
    }

    Como ela foi declarada como inline, ao chamá-la por exemplo como:

    var = soma(3, 5);

    O compilador vai substiuir por:

    var = 3 + 5;
    • Vetores de tamanho variável

    Na C99 você pode declarar um vetor assim:

    void funcao(int numero)
    {
    	char vet[numero];
    }

    O tamanho do array de char vet depende do parâmetro recebido. Isso era resolvido antes com funções para alocação dinâmica.

    Há adições também do tipo complex, para números complexos (e imaginários) e muito mais novidades. No site do WG14 [2] há toda a especificação para download.

    Para utilizar a C99 com o gcc, use:

    $ gcc -std=c99 fonte.c

    4. Recursos da C11

    • Novas funções seguras (com bound checking)

    As funções tidas como inseguras como strcpy, strcat etc ganharam novas versões sufixadas com “_s”, de safe. Basicamente elas recebem um argumento a mais que é o número de caracteres para trabalhar. São elas:

    • Multithreading (thread.h)

    Apesar de os SOs modernos fornecerem funções de API para multithreading, ao ser inserida oficialmente no C possivelmente vai ajudar na portabilidade de aplicativos que utilizem este recurso. É algo muito bom para a linguagem.

    • Estruturas e uniões anônimas

    Útil quando estão aninhados, este foi um recurso que senti falta quando programei a primeira versao do pev, que usa muitas estruturas. Se queremos que um campo de uma estrutura seja outra estrutura, antes da C11, é preciso fazer:

    struct ponto {
    	int x;
    	int y;
    }
    
    struct reta {
    	struct ponto p;
    }

    Aí para acessar:

    struct reta r;
    
    r.p.x = 10;

    Com a C11, a estrutura reta pode ser:

    struct reta {
    	struct ponto;
    }

    E o acesso:

    r.x = 10;

    Dizemos então que a estrutura reta possui uma estrutura anônima (sem nome) como campo.

    5. Qual especificação usar?

    Na hora de usar uma especificação e seguir suas regras, você deve pesar como é o seu projeto, em quais sistemas ele vai rodar, como será distribuído etc. Por exemplo, se é um projeto interno e o compilador que você utiliza suporta a C99, não vejo por que não utilizá-la. Agora, se você vai distribuir seu código, aí já tem que pensar nas versões dos compiladores de quem for baixar. Será que o gcc do usuário do seu software é a última versão? Será que suporta a C11?

    No hdump, um dumper hexa/ASCII que fiz para usar no Windows e no Solaris, eu optei por trabalhar com C89 por dois motivos: o código é pequeno e eu precisava levar o fonte para compilar em versões antigas do Solaris, com compiladores que não suportavam a C99, por exemplo.

    Já para o pev um analisador de binários de Windows, eu comecei em ANSI mas já estou atualizando para C99, para deixar o código mais legível e utilizar os novos recursos. Eu distribuo o fonte e binários já compilados, inclusive para Windows, então não tem problema para os usuários.

    O importante é ler as especificações e perceber se tem algo que te ajuda/atrapalha no projeto, pensando no escopo, usuários e sistemas alvo. Só a C11 que acho que ainda não dá para distribuir um código em produção, visto que nenhum compilador à suporta completamente. Recomendo usá-la internamente e esperar um pouco antes de liberar código com ela, pois está muito recente.

    6. Conclusão

    Existem outros recursos interessantes na C11 [3] mas ainda não conheço um compilador que a suporte 100%. Na verdade o gcc também não suporta a C99 por inteiro ainda. Existe até uma página onde você pode acompanhar as mudanças no suporte à C99 [4]. Na versão 4.6, o gcc já estreiou o suporte inicial à C11, sob a opção -std=c1x. A versão 4.7 já vai vir com suporte a mais recursos desta especificação, sob a opção -std=c11. Inslusive cabe um comentário: é muito bom poder contar com um conjunto de compiladores constantemente atualizado e bem feitos como são os do GCC. Essa maravilhosa suíte inclui compiladores para C, C++, Objective-C, Fortran, Java, Ada, e Go. Todos livres. É um espetáculo mesmo. :)

    O último rascunho ISO/IEC 9899:201x, divulgado em abril do ano passado está disponível em PDF [5] mas a especificação mesmo, publicada em dezembro, está à venda por cerca de R$ 450,00 na ISO Store [6].

     

    Referências

    [1] https://groups.google.com/forum/?fromgroups#!forum/exploits-brasil
    [2] http://www.open-std.org/JTC1/SC22/WG14/
    [3] http://en.wikipedia.org/wiki/C11_%28C_standard_revision%29
    [4] http://gcc.gnu.org/gcc-4.6/c99status.html
    [5] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
    [6] http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=57853

    Entre para seguir isso  


    Feedback do Usuário

    Join the conversation

    You can post now and register later. If you have an account, sign in now to post with your account.

    Visitante

  • Conteúdo Similar

    • Por Candeer
      Olá! No artigo anterior falamos sobre Signals, que é de suma importância para a comunicação entre processos, mas para construir o nosso debugger precisamos muito mais do que apenas isso, precisamos de fato ter total controle sobre um dado processo e se possível controlar até o seu própio início.
      Neste artigo será explicado o que são forks e seu uso em desenvolvimento de aplicações em sistemas UNIX. Sem mais delongas, vamos prosseguir!!!😀
      Resumidamente a syscall fork é usada para a duplicação e criação de um processo. Quando um dado processo chama a função fork(), é criada uma cópia idêntinca de seus dados. Note que apenas uma cópia é feita, o processo filho não compartilha o mesmo espaço de memória do pai.
      A syscall fork retorna um PID que é usado para indetificar em qual processos estamos e também dar acesso ao ID do processo filho. Caso o PID seja 0 estamos executando no filho, caso seja qualquer outro somos o processo pai, isso ocorre pois o pai precisa saber o PID do filho, mas o filho não necessariamente precisa saber o seu própio (da mesma maneira que o seu processo não sabe o própio PID ao menos que o mesmo peça).
      Algo interessante de se notar é que os Init System usados para subir e gerenciar serviços de sua máquina trabalham dessa mesma maneira, você pode checar sua árvore de processo usando comando pstree:
      $ pstree Dessa maneira você tem uma representação bem visual de como está dividida a sua estrutura de processos 😀. Note que todos os processos são filhos do seu Init system (seja ele SystemV, Systemd, etc). Aconselho você explorar o comando pstree para uma visão bem mais detalhada do seu sistema! Outra abordagem é usar o própio comando ps:
      $ ps -ef Rode o comando acima (dependendo da quantidade de processos use um pipe para o less 😉) e com ele teremos uma visão mais detalhada. A coluna PID representa o ID do processo em si e a coluna PPID representa o "Parent Process ID", que nada mais é que o ID do processo pai. Note que o PID 1 é o seu Init System e os seus processos rodam como filho dele!

      Vale notar que o processo Pai do própio init é o PID 0, que é conhecido como "swapper" ou "scheduler", que é o processo responsavel para realização de paging. Paging é o sistema de gerenciamento de memória que salva os dados da RAM em uma memória secundária (HD, SSD e etc) e recupera em formato de páginas (outros PID também são filhos do propio PID 0 como PID 2 que gerencia todas as threads que rodam em Kernel Land(KThread) etc).
       
      Programando Forks
      A syscall fork está na lib  <unistd.h> (Unix Standard library) e tem a seguinte construção:
      #include <sys/types.h> #include <unistd.h> pid_t fork(void); Precisamos incluir a lib <sys/types.h> para que seja possivel acessar o tipo pid_t. A função fork não espera nenhum parâmetro para a sua construção e o código abaixo demonstra o quão simples é cria um fork.
      #include <stdio.h> // Acesso a syscall #include <unistd.h> // Acesso ao tipo variavel pid_t #include <sys/types.h> int main(void) { int x; printf("Processo normal...\n"); printf("Forking...\n"); sleep(5); pid_t pid = fork(); x = 40; if (pid == 0) { printf("Eu sou o processo filho meu PID: %d\n", pid); } else { printf("Eu sou o processo pai de %d\n", pid); } sleep(5); return 0; } Compile o código acima da seguinte forma:
      $ gcc -o fork fork.c $ ./fork Note que o código se "divide" a partir da chamada fork e um if  é usado para saber se estamos executando no pai ou no filho, note também que o pai sabe o PID e o filho não.
      Para melhor visualização o código acima roda por 10 segundos (por conta da chamada ao sleep com esse tempo de espera). Abra um outro terminal e rode o comando:
      $ watch -n1 pstree O comando acima vai executar o pstree a cada 1 segundo, desta forma você verá o exato momento da criação do fork.

      Comunicando-se com o processo fork
      Agora imagine que um  processo precisa esperar o seu filho terminar algum trabalho e dependendo do seu sinal o processo pai realiza alguma ação. A comunicação entre o processo pai e o filho se da por signals. O pai pode saber exatamente o estado do seu processo filho usando a syscall wait e waitpid, ambas na lib <sys/wait.h>:
      #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options); A syscall wait espera que ao menos 1 de seus processos filho troque de estado, já a waitpid espera por um processo específico. Como sabemos exatamente qual processo queremos rastrear iremos usar esta call 😀:
      #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main(void) { printf("Spliting work...\n"); pid_t pid = fork(); if (!pid) { int a = 0; for(int i = 0; i < 100000000; i++ ) { a += i*2 + 10 *i; } return 9; } int status; int signal; printf("Waiting child finish work...\n"); waitpid(pid, &status, 0); if (WIFEXITED(status)) { signal = WEXITSTATUS(status); printf("Child exited, status = %s\n", strsignal(signal)); } return 1; } Compile o código acima e execute:
      $ gcc -o work work.c $ ./work Spliting work... Waiting child finish work... Child exited, status = Killed Veja que após a chamada de fork nosso processo filho executa várias iterações e realiza um cálculo (um cálculo totalmente randômico) e após isso retorna 9. Este retorno em questão é apenas por motivos educativos (no artigo anterior falamos de sinais e como eles funcionam). O processo pai usa a syscall waitpid para esperar que qualquer signal seja enviada do pid especificado. Após receber um status é verificado se o fork saiu (WIFEXITED) e se sim, pegamos o signal enviado usando WEXITSTATUS(status da saída) e usamos a chamada strsignal(provida pela string.h) para recuperar uma versão em texto do signal. Nesse caso iremos recuperar o signal "KILLED", pois colocamos 9 apenas por razões educativas.
      Normalmente se tudo ocorreu bem colocamos 0 (inclusive é dessa maneira que sua shell avalia se o programa rodou certo).
      $./work && echo "Filho saiu com 0, tudo certo..." || echo "Filho saiu com 1, algo errado..." No caso acima a nossa shell irá criar um fork do nosso work, executar o nosso programa (que por sua vez também executa um fork mas não entra em questão aqui) e se o signal retornado pelo fork for 0 ele imprime uma mensagem, caso contrario ele imprime uma mensagem de erro, dessa maneira você pode orquestrar um shell scripting usando o própio retorno do processo 😉
      Tente mudar o retorno do fork acima e verifique seu status usando funções providas pela <sys/wait.h>. No exemplo acima usamos apenas a call WIFEXITED e WEXITSTATUS, mas existem várias outras.
      Forks são de extrema importância para criação e gerenciamento de processos e iremos usar forks para que seja possível executar o programa que queremos debugar, dessa maneira o software em questão vai ser filho do nosso debugger, o que nós da total controle sobre o mesmo.
      Comentarios são todos bem vindos e todos os códigos usados estão disponíveis no github! 😀

      Links úteis:
          Process Control
          fork
          wait
          Process State
          Fork Bomb - Cuidado com isso
    • Por void_
      Depois de um bom tempo programando em C e adquirindo uma paixão legítima por Assembly (essa a nem tanto tempo assim, infelizmente), resolvi dar mais uma chance a programação de alto nível. Como já tenho uma boa base em C, achei apropriado partir para o C++, já que nunca me dei bem com Java, contudo os livros não estão me animando muito. Eu comecei lendo o livro oficial do C++ (do Bjarne) e também o C++ Primer, considerado um best seller desde a primeira edição. Foi um sofrimento e tanto ler esses livros. Em ambos foram mais de cinquenta páginas de filosofia verborrágica para começar a entrar no bê-a-bá da linguagem (que obviamente me fez desanimar antes de chegar na metade). Achei esses livros muito mal estruturados. A impressão que passa é que os autores não querem ensinar nada, porque eles fazem questão de deixar o livro o mais enfadonho possível. Não digo que não é importante ensinar a parte "filosófica" da linguagem, mas acredito que seria melhor nivelar entre a prática e a teoria de forma progressiva. Enfim, não sei se sou eu que sou chato e impaciente demais ou se o problema são os livros, porque quando finalmente chega na parte "interessante", a leitura já me drenou toda a energia e vontade de continuar a leitura, aí começam os bocejos, a canseira na mente e na vista, etc. A coisa piora ainda mais porque, quando enfim chega na prática, 50% do conteúdo é herança do C que você (no caso eu) já sabe.
      Outro problema é a desatualização do material em português que temos acesso aqui. Enquanto no estrangeiro o pessoal trabalha com a versão 17, ainda estamos na 11, no máximo 14.
      Abri esse tópico para pedir alguma indicação de algum livro de C++ que fuja das questões que eu citei.
    • Por Candeer
      Olá, neste artigo compartilharei um pouco da minha pesquisa no desenvolvimento de debuggers. No momento estou trabalhando em um protótipo de debugger para Linux, mas nada tão avançado quanto um gdb ou radare (muitas coisas são necessárias para chegar neste nível de maturidade de software).
      O desenvolvimento de debuggers é uma atividade muito interessante, já que, em sua forma mais básica, pode ser resumido em uma série de chamadas de sistema (syscalls) para que seja possível o controle do processo a ser depurado (muitas vezes chamado de debuggee) e de seus recursos, mas não vamos colocar a carroça na frente dos cavalos e vamos em partes.
      Antes de começarmos a discutir detalhes mais específicos acerca da depuração de processos, é necessário um entendimento básico de como os mesmos se comunicam na plataforma que vamos desenvolver o tal debugger, no nosso caso, UNIX-like.
      Inter-process communication (IPC)
      IPC é uma forma que processos podem utilizar para se comunicar dentro de um sistema operacional. Existem diversas maneiras de comunicação: via sinais (signals), sockets, etc, mas para a criação de um debugger é apenas necessário usar sinais para a execução.
      Sinais funcionam como uma notificação que pode ser enviada à um processo específico para avisar que algum evento ocorreu.
      É possível também programar um processo para reagir aos sinais de maneira não padrão. Se você já teve um uso razoável de Linux, você provavelmente já enviou sinais à um processo. Por exemplo, quando você aperta Ctrl+C para interromper a execução de um processo, é enviado um sinal do tipo SIGINT, que nada mais é que uma abreviação para Signal Interruption. Se o processo em questão não está preparado para reagir a este sinal, o mesmo é terminado. Por exemplo, considere o seguinte código:
      #include <stdio.h> int main(void) { while(1) printf("hi\n"); return 0; } Ao compilar e executar o código acima e apertar Ctrl+C, o mesmo encerra como esperado, porém podemos verificar que um SIGINT foi enviado usando a ferramenta ltrace, que além de listar chamadas a bibliotecas também mostra os sinais enviados ao processo:
      $ gcc -o hello hello.c $ ltrace ./hello Rode o comando acima e aperte Ctrl+C para verificar o sinal enviado!
      Programando reações a sinais
      A capacidade de enviar sinais a um processo nos dá a possibilidade de saber o que esta acontecendo com algum processo específico que estejamos depurando.
      Para programar reações a algum tipo de sinal, podemos incluir a biblioteca signal, para que possamos usar a função e estrutura (struct) sigaction:
      struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); };  
      int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); A struct sigaction nos permite adicionar handlers (tratadores) para nossos sinais, enviando o endereço de nossa função que realiza algum tipo de ação baseada no sinal enviado para o campo sa_handler(sigaction handler).
      Um handler neste contexto nada mais é que uma função que sempre vai ser chamada quando um dado sinal for enviado, dessa maneira podemos executar alguma ação quando recebermos um sinal.
      Já a função sigaction recebe o número do sinal, porém uma série de macros já são pré-definidas e podemos passar como argumento apenas o nome do sinal, como SIGINT por exemplo. A função recebe também a referência da struct previamente definida (struct sigaction) e, caso precise trocar um handler por outro, também recebe no último argumento (oldact) o handler anterior, para que possa ser feita a troca pelo novo. Como não é o nosso caso, vamos passar NULL neste último argumento.
      O código abaixo simula um uso de handlers de sinais, que imprime uma mensagem quando um sinal é enviado:
      #include <stdio.h> #include <signal.h> #include <unistd.h> // sleep void simple_handler(int sig) { printf("Hello SIGINT\n"); } int main() { struct sigaction sig_handler = { simple_handler }; sigaction(SIGINT, &sig_handler, NULL); sleep(1000); return 0; } Ao executar o código acima, aperte Ctrl+C e veja que será imprimido a mensagem do nosso handler!
      O manual da signal contém uma tabela com todos os sinais usados por sistemas POSIX.
      Para enviarmos sinais facilmente em sistemas UNIX podemos usar o comando kill:
      $ kill -l O comando acima mostra todos os sinais e seus respectivos números, com isso podemos fazer algo interessante. Por exemplo, rode o código acima em um terminal separado e use o kill para se comunicar com o seu processo, assim:
      $ ps ax | grep simple_signal $ kill -2 <pid> Primeiro buscamos o PID do nosso processo então usamos o kill que espera como primeiro argumento numero do sinal (listado em kill -l) e o segundo o PID do processo alvo.
      Ao enviar o sinal, podemos ver que o nosso código reage aos sinais que foram associados a um handler especifico! Tente criar handlers para vários sinais e teste usando o comando kill. 😃
      Abaixo um código para demonstrar um uso real de um software que escreve dados aleatórios nos arquivos temporários e antes de uma finalização abrupta, é deletado o que foi usado:
      #include <stdio.h> #include <signal.h> #include <unistd.h> // Log errors void fatal(const char* err_msg) { fprintf(stderr, "Error: %s\n", err_msg); } // Escreve algo random em um arquivo void random_work() { FILE* temp_files = fopen("/tmp/foo", "w"); if (!temp_files) { fatal("Cant open foo!"); } else { fprintf(temp_files, "%s", "Random random random!\n"); fclose(temp_files); } } // Handler para deleta arquivos criados void handler_termination(int sig) { // Verifica se existe usando a function access // Caso existe usa a syscall unlink para remover o arquivo if (access("/tmp/foo", R_OK) < 0) return; unlink("/tmp/foo"); printf("All clean! closing...\n"); } int main() { //struct sigaction que recebe a function handler_termination como valor do seu handler struct sigaction interruption_handler; interruption_handler.sa_handler = handler_termination; // Syscall sigaction que associa o nosso handler para um sinal especifico // O ultimo campo NULL, espera o handler anterior para que posso tornar o novo handler o default sigaction(SIGINT, &interruption_handler, NULL); random_work(); sleep(1000); handler_termination(0); return 0; } Dica: Dê uma olhada na tabela de sinais e crie handlers para o mesmo código acima!
      Para a construção do nosso debugger iremos focar mais no signal SIGTRAP, para que seja possível detectar se o nosso processo sofreu uma "trap" da CPU. Uma trap ocorre quando acontece alguma interrupção síncrona na execução, que faz o processo ficar parado até que o sistema operacional execute alguma ação. Isto será usado para implementar e interpretar breakpoints. Veremos tudo isso com mais detalhes em breve!
      Sinta-se livre para comentar e sugerir correções e melhorias. Até o próximo artigo!
      Links úteis:
      Syscall IPC CERO 11 – Linux Syscalls Syscalls, Kernel mode vs User mode Programação em C
    • Por Eduardo Cintra
      Alguém me indica um bom livro para aprender programação utilizando sockets em C? Pode ser em inglês.

      Desde já agradeço!
    • Por HiroMarikame
      Olá, bom dia/tarde/noite m/
      Pessoal fiz um script para me gerar paginas estaticas usando python. SEI QUE EXISTEM PLATAFORMAS que não exigem conhecimentos tecnicos para criar sites mas aproveitei a oportunidade para estudar algo interessante para minha pessoa...
       É algo simples mas que rendeu a aplicação de conceitos como OOP, Ajax, JSON, requests etc.
      A ideia é que ele seja simples, então tentei simplificar utilizando JSON. Porém, mesmo assim há algumas "complicações", ainda tenho que escrever parte do código HTML para colocar o conteudo como: textos, imagens etc. sem contar que o JSON tem uma limitação quanto ao tamanho da string,  ou seja, é dificil manter um texto estruturado no JSON.
      Há erros, tanto no código quanto no design. E não sou bom com design portanto tentei fazer o basico do basico.
      Quem estiver disposto a contribuir para melhorar esse pequeno projeto é só seguir o link do github \m/ : https://github.com/HiroTatsuo/Static-Project
      PS: Qualquer coisa implementada será estudada por mim, então não será uma contribuição para o projetinho apenas. mas sim para meus conhecimentos '--' rsrsrs
      Esse é um print de como ficam as paginas.

       
×
×
  • Criar Novo...