IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Diskuze: Cteni/prevod binarnich souboru do citelne podoby

Aktivity
Avatar
Patrik Pastor:1.9.2019 13:50

Chci vedet jak bezpecne precist binarni soubor kdyz nenzam datovy typ

Zkusil jsem: Chtel jsem si ze srandy vypsat obsah sveho programu.out (tedy po kompilaci programu.c) - ktery je, jak predpokladam binarni. Jenomze nevim jaky datovy typ se v nem nachazi. Proto jsem zkusil nasledujici:

#include <stdio.h>
#include <stdlib.h>

int main (void) {

   char* mp1000 = (char*)malloc(1000);
   //temp.c - soubor po kompilaci meho c programu, binarni soubor
   //alokovani pole pro neznamy typ - 1000 bytu (domnivam se, ze bych to mohl zobrazit jako znaky - proto char
   FILE* fpTemp = fopen("temp.c", "rb"); //pointer na soubor 'temp.c'

   fread(mp1000, sizeof(char), 1000, fpTemp);
   fclose(fpTemp);

   for(int i =0; i<1000 ; i++)
      printf("%c ",*(mp1000 + i));

   free(mp1000);
   return 0;
}

kde 'temp.c' - je binarni soubor po kompilaci programu.

vystup:

Press ENTER or type command to continue
 E L F               >      �        @        ` B           @  8
                                                                    @  "  !          @        @        @        h        h                       �        �        �                                                                �        �                                                �        �                                                   X        X                        � -       � =       � =       �        �                        � -       � =       � =       �        �                       �        �        �        D        D                P � t d                                <        <                Q � t d                                                     R � t d     � -       � =       � =       p        p                / l i b 6 4 / l d - l i n u x - x 8 6 - 6 4 . s o . 2              G N U  � � � � = t � � �  �  � ) � � D  T �             G N U
                                      �
                                                     � e � m                             M                        ^                         &                                                                        ;                        z
Press ENTER or type command to continue

takze, jaky datovy typ mam pouzit (pro alokovani pameti jakeho datoveho typu?) kdyz nevim z ceho se nacitany soubor sklada (aka neznam datovy typ, ktery je v souboru obsazen)? - a tato situace muze prece nastat hodnekrat, kdyz se snazim nacist soubor od nekoho zvenci.

PS: nemel by snad byt output meho souboru citelnejsi? (kdyz sem nacitaval tady cisla z lekce, tak jsem je dostal v citelne podobe) - jak by sel ten binarni soubor prevest na 1 a 0 ?

 
Odpovědět
1.9.2019 13:50
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:1.9.2019 16:48

Takto na to nahlížet nemůžeš. Těžko můžeš vyextrahovat data z binárního souboru, pokud neznáš přesně jeho strukturu. Binární reprezentace programu lajkovi nic neřekne. Binární podoba je určena pro datové soubory. Jinač zdrojové soubory s koncovkou .c jsou textové soubory nikoli binární. Soubor, který obsahuje přeložený tvar programu se nazývá cílový(objektový, relativní). Po sestavení výsledného programu z cílového (cílových) souboru a knihoven vzniká spustitelný soubor.

Podívejme se na následující příklad, ve kterém necháme uživatele zadat dlouhé celé číslo. Toto číslo zapíšeme do souboru nums.dat v binární podobě. Poté toto číslo ze souboru nums.dat načteme a zobrazíme ho na obrazovce.

#include <stdio.h>
#include <stdlib.h>

void errmsg(char *msg, int code);

int main(void) {
        FILE *fp;
        long int num;

        // cteni cisla
        printf("Zadej cele cislo (long): ");
        scanf("%ld", &num);

        // zapis do souboru
        if ((fp = fopen("nums.dat", "wb")) == NULL) {
                errmsg("Chyba pri otevirani souboru (zapis)", 1);
        }

        if (fwrite(&num, sizeof(long), 1, fp) != 1) {
                errmsg("Chyba pri zapisu dat do souboru", 1);
        }
        if (fclose(fp) == EOF) {
                errmsg("Chyba pri uzavirani souboru (zapis)", 1);
        }

        // reset cisla
        num = (num == 0L) ? 1L : 0L;

        // cteni ze souboru
        if ((fp = fopen("nums.dat", "rb")) == NULL) {
                errmsg("Chyba pri otevirani souboru (cteni)", 1);
        }

        if (fread(&num, sizeof(long), 1, fp) != 1) {
                errmsg("Chyba pri cteni dat ze souboru", 1);
        }

        if (fclose(fp) == EOF) {
                errmsg("Chyba pri uzavirani souboru (cteni)", 1);
        }

        // vypis cisla
        printf("Ulozene cislo je %ld", num);

        return 0;
}

void errmsg(char *msg, int code) {
        fprintf(stderr, msg);
        exit(code);
}

Takto se zapisují a čtou data do souboru v binární podobě.

Když si pak otevřeš soubor nums.dat, uvidíš binární reprezentaci čísla. Ať zadáš 0 nebo 12345678, bude velikost souboru nums.dat bude pořád stejná (sizeof(long)).

Nahoru Odpovědět
1.9.2019 16:48
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Odpovídá na DarkCoder
Patrik Pastor:1.9.2019 17:12

Neni to presne odpoved na mou otazku (jak presne bys parsovar/extrahovat binarni soubor bez znalosti jeho vnitrni struktury), nicmene mam k tvemo kodu nekolik dotazu:

  1. fwrite(&num, sizeof(long), 1, fp) != 1) - jak to ze mas navratovou hodnotu -1, kdyz v dokumentaci je napsane

"If an error occurs, or the end of the file is reached, the
return value is a short item count (or zero)" ( nikde se nepise o zaporne hodnote - to same plati u funkce (fread))

  1. jak to ze mas inicializaci funkce nahore pred main funkci void errmsg(char *msg, int code); - ale jeji vlastni implementaci (telo funkce) mas pod main funkci?
  2. proc si ji vlastne vytvarel (funkci errmsg, kdyz je ve standardni knihovne funkce perror, ktera dela to same - navic specifikuje, o jaky error se jedna - int errno - z dokumentace)
  3. tento zapis podminky ne zcela rozumim:

num = (num == 0L) ? 1L : 0L; - chapu ho jako, kdyz je num 0 potom je 1 jinak 0. Ale proc by mel byt 1 (v pripade ze je 0) kdyz jej chce "resetovat"?

Nakonec by me jen zajimalo, - to co jsem se ptal na konci - jak lze binarni soubor prevest do podoby jednicek a nul (pokud to vubec jde), ale alespon tak, aby mi to editor neprekladal jako ASCII hodnoty - tedy nevyskakovaly mi z toho blbosti, ale aspon v nejake podobe bych ho chtel dostat. (nevim jaky je v bash program pro to, ale kdyz bys si zastavil kompilaci pred linkovanim - tedy ve fazi objektoveho souboru - dostanu program v ASM - ale rad bych kod prevedl do sestnackove soustavy nebo binarky klidne, nikde jsem na to zatim nenasel programek).

 
Nahoru Odpovědět
1.9.2019 17:12
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:1.9.2019 17:52

Odpověď byla ve druhé větě - Nelze. Když Ti pošlu binárně zapsaný soubor o velikosti pouhých 10 bytů, nepoznáš bez znalosti struktury toho souboru, co tam mám zapsané (mám tam dvě int čísla a short?, mám tam short a dvě long čísla?, mám tam 5 short čísel nebo 10 znaků? Nevíš).

  1. návratová hodnota funkce fwrite() je počet úspěšně načtených prvků. Proto se testuje na hodnotu 3 argumentu funkce. Pokud bude návratová hodnota odlišná, nastala chyba. Na -1 nikde netestuji.
  2. To co je vně hlavního programu je prototyp funkce. Prototyp je deklarací funkce. To mi říká, jak funkce errmsg() vypadá. Kolik má parametrů, jaké typy, jaká je návratová hodnota. Umožňuje to překladači odhalit chybu v programu při zadání nesprávných typů a počtu argumentů. Definice samotné funkce je pak ve spodní části programu.
  3. Ano, dalo by se použít i perror(). Funkce perror() nevrací chybový kód, pouze popis chyby. Chyba je vrácena v proměnné errno. Takže to úplně to samé není. Pokud bych chtěl bližší info o chybě, pak je použití perror() vhodnější. V příkladu mi postačila informace, že došlo k chybě v dané části, proto jsem použil své vlastní řešení.
if ((fp = fopen("nums.dat", "rb")) == NULL) {
                perror("Popis chyby");
                printf("Cislo chyby: %d\n", errno);
                exit(errno);
 }

Lepší by bylo použít fprintf() s výstupem do chybového streamu.

  1. Resetování čísla má pouze pedagogický charakter, ukazuji tím, že hodnota bude jiná než ta, která byla zadána. Resetováním se tak nemyslí nastavování vždy na nulu ale na hodnotu odlišnou.

příkaz:

num = (num == 0L) ? 1L : 0L;

je totéž co:

if(num == 0L) num = 1L;
else num = 0L;

Smyslem je nastavit na hodnotu odlišnou, než ta, která byla zadávána. Aby se při výpisu ukázalo, že funkce fread() pracuje tak jak má.

Proč by to nešlo, ale žádný důvod k tomu není. Binární reprezentace je vnitřní záležitostí počítače, je zcela zbytečné převádět znak do podoby 0 a 1. Ale pokud to chceš, již jsem to psal v jednom příspěvku.

Jádrem toho všeho je umět převést znak do binární podoby.

for (int i = CHAR_BIT - 1; i >= 0; i--) cout << ((c >> i) & 1);

Následující kód ukazuje výpis binární reprezentace řetězce na obrazovku.

unsigned char text[] = "www.itnetwork.cz", *ptext = text;
while (*ptext) {
        for (int i = CHAR_BIT - 1; i >= 0; i--) cout << ((*ptext >> i) & 1);
        ptext++;
}

Znovu podotýkám, že tato podoba je pro člověka nepoužitelná. Pouze ukazuje, jak počítač bere daný znak.
Akceptované řešení
+20 Zkušeností
+2,50 Kč
Řešení problému
Nahoru Odpovědět
1.9.2019 17:52
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Odpovídá na DarkCoder
Patrik Pastor:1.9.2019 18:38

hazi mi to akorat error ze cout neni deklarovane - coz neni, nikde nevidim co to je za promennou a preprocesor ji ocividne neexpanduje, takze to neni ani makro.

A jeste k predchozimu kodu - na konci implementace tve error funkci

void errmsg(char *msg, int code) {
        fprintf(stderr, msg);
        exit(code);
}

Mi kompilator dava warrning :

format not a string literal and no format arguments [-Wformat-security]

nevim co presne to znamena, ale rekl bych, ze kdyz pouzivas printf(sterr,msg) - tak nemas definovany format, coz asi ta funkce vyzaduje. Neni to error, ale dava mi to upozorneni

 
Nahoru Odpovědět
1.9.2019 18:38
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:1.9.2019 19:11

cout je předefinovaný datový proud, který je při spuštění programu C++ automaticky připojen ke konzoli. Jelikož se bavíme o čistém C, tak funkce bude vypadat následovně:

#include <stdio.h>
#include <limits.h>

int main(void) {

        unsigned char text[] = "www.itnetwork.cz", *ptext = text;
        while (*ptext) {
                for (int i = CHAR_BIT - 1; i >= 0; i--) printf("%d", (*ptext >> i) & 1);
                ptext++;
        }

        return 0;
}

Funkce errmsg() je naprosto v pořádku, vyžaduje aby do programu byly vloženy dva hlavičkové soubory stdio.h kvůli fprintf() a stdlib.h kvůli exit().

#include <stdio.h>
#include <stdlib.h>

void errmsg(char *msg, int code);

int main(void) {

        errmsg("Nastala chyba.\n", 1);

        return 0;
}

void errmsg(char *msg, int code) {
        fprintf(stderr, msg);
        exit(code);
}
Nahoru Odpovědět
1.9.2019 19:11
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Odpovídá na DarkCoder
Patrik Pastor:1.9.2019 19:37

Co presne znamena (*ptext >> i) & 1)

Chapu ze ve for loopu projizdim cely char, ale jak syntakticky zapsano "projizdej po bitu?" neni to prave ono? a co znamena operator >>, nebo co je adresa jednicky (&1) - ve funkci printf?

 
Nahoru Odpovědět
1.9.2019 19:37
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:1.9.2019 20:00

Ano, je to procházení jednotlivých bitů daného bytu. >> je operátor posunutí, konkrétně vpravo. Posun vpravo odpovídá dělení dvěma, posun vlevo násobení dvěma.

pokud bychom testovali písmeno 'A', které je v binární podobě 01000001, pak program funguje následovně.

první iterakce je následující:

('A' >> 7) & 1

Posunem o 7 bitů vpravo se dostaneme na nejvzdálenější bit znaku 'A' (ten se nachází úplně vlevo a je to 0). Výsledek binárního součinu 0 a 1 je 0, vypíše se 0. Dalším bitem je 1 (druhý bit zleva), výsledkem binárního součinu 1 a 1 je 1, vypíše se 1. Takto to pokračuje dál až k nultému bitu, což je 1. Opět výsledkem bitového součinu 1 a 1 je 1, vypíše se 1.

Proto pro písmeno 'A' se vypíše 01000001. for cyklus končí a inkrementuje se ukazatel na další znak v řetězci (pokud existuje). Ukončení provádí test na nenulový znak. Tím se získá binární reprezentace znaků tvořícího celý řetězec.

& je operátor bitového součinu (AND). Výsledkem operace je buď 0 nebo 1, podle toho zda-li oba operandy jsou 1 (výsledek bude 1), v ostatních případech bude výsledek 0.

Nahoru Odpovědět
1.9.2019 20:00
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Odpovídá na DarkCoder
Patrik Pastor:1.9.2019 20:08

jenom nechapu kdyz rikas o 7 doprava, ale dostanem se na nejvzdalenejsi bit uplne VLEVO, to je jako naopak jenom jak?

 
Nahoru Odpovědět
1.9.2019 20:08
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:1.9.2019 20:48

Je třeba pochopit jak bitový posun funguje. Posun bitů o X míst doprava je skutečně posun bitů o X míst doprava. Bity které se dostávají mimo rozsah se ztrácejí a z druhé strany se doplňuje nula.

Pokud budu mít písmeno 'A' a neprovedu na něm žádný posun ('A' >> 0), bude jeho hodnota v binární podobě 01000001. Pokud provedu posun o 1 vpravo ('A' >> 1), posunou se bity o jedna vpravo. A jednička na nejdražším bitu zmizí a zleva se doplní nula. Výsledek pak bude 00100000.

Když se pak vyhodnocuje příkaz:

printf("%d", (*ptext >> i) & 1);

bere se hodnota z nejdražšího bitu (výraz v závorce), který pak slouží jako jeden z operandů bitového součinu.

Podívej na příklad, který ukazuje, jak se mění bitová hodnota binární podoba znaku při provedení operace bitového posunu vpravo:

#include <stdio.h>
#include <limits.h>

int main(void) {

        char c = 'A';

        for (int j = 0; j < CHAR_BIT; j++) {
                printf("A >> %d: ", j);
                for (int i = CHAR_BIT - 1; i >= 0; i--) printf("%d", (c >> i) & 1);
                putchar('\n');
                c >>= 1;
        }

        return 0;
}

Binární podoba znaku je pak dána na pozici nejdražšího bitu od maximálního posunutí po nulové.

Nahoru Odpovědět
1.9.2019 20:48
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Odpovídá na DarkCoder
Patrik Pastor:5.9.2019 19:52

stale ale nechapu jednu vec:

for (int i = CHAR_BIT - 1; i >= 0; i--) printf("%d", (c >> i) & 1);
  • tento loop. Jak to ze zacinas od 7? (i = CHAR_BIT - 1), Jako by jsi posouval zezacatku o 7 bitu doleva (c >> i) - kde i je 7 (ze zacatku - tak jak jsi definoval v loopu).

Ptam se na to, jak je mozne ze promenna i v cyklu jde od 7-0 ale vypis je od 0-7 ... tedy obracene. Prece kdyz to posunes o 7 a posunul bys vsechny bity tak je vynulujes ne? Furt se na to divam a nemuzu to za boha pochopit

 
Nahoru Odpovědět
5.9.2019 19:52
Avatar
Odpovídá na Patrik Pastor
Patrik Pastor:5.9.2019 19:59

oprava - uz jsem pochopil, ze vlastne porovnavas (bitovy AND) bit na konci bytu charu 'A' - a posunujes se dopredu. Potom se teda ptam proc zacinas odzadu bytu? proc zacinas porovnavat u posledniho bitu a pokracujes k prvnimu. proc nezacnes od prvniho k poslednimu?

 
Nahoru Odpovědět
5.9.2019 19:59
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:5.9.2019 20:44

Binární podoba čísla se mění zprava doleva. Bit stojící nejvíce vpravo je označován jako nejdražší bit, bit stojící nejvíce vlevo je označován jako nejlevnější bit. 1 slouží jako bitová maska, ta reprezentuje podobu 00000001. Díky tomu pomocí bitového součinu (AND) zjišťuji změnu pouze na nejdražším bitu. Výsledkem může být buď 0 nebo 1. Znak 'A' je v binární podobě 01000001. Bitový součin 01000001 & 00000001 je 1. Tím ale získávám hodnotu nejdražšího posledního bitu. Abych získal hodnotu na nejlevnějším prvním bitu, musím provést maximální bitový posun (o 7 míst). To pak testuji 00000000 & 00000001, což je nula. Posunem o 6 míst dostáváme podobu 00000001. Po bitovém součinu s hodnotou 00000001 se získává hodnota 1, to je pozice druhého nejlevnějšího bit. Proto se testuje od maximálního posunu po nulový, čímž se získá binární podoba daného znaku.

Nahoru Odpovědět
5.9.2019 20:44
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Odpovídá na DarkCoder
Patrik Pastor:6.9.2019 9:09

to same by ale potencialne slo i od nejlevnejsiho bitu po nejdrazsi, tedy naopak. Nebo jaky by byl vysledek, kdyby jsem posunoval od nejlevnejsiho po nejdrazsi (zleva doprava - naopak).?

 
Nahoru Odpovědět
6.9.2019 9:09
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:6.9.2019 10:50

Nešlo. Je třeba si uvědomit, jaký by byl výsledek bitového součinu. Pokud bych posunoval doleva a použil bitovou masku 128 (10000000 binárně), pak by se na místech obou jedničkových bitů zobrazovala hodnota 128.

Následující příklad nepracuje správně:

#include <stdio.h>
#include <limits.h>

int main(void) {

        char c = 'A';

        for (int i = 0; i < CHAR_BIT; i++) printf("%d", (c << i) & 128);

        return 0;
}
Nahoru Odpovědět
6.9.2019 10:50
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:6.9.2019 12:55

Takto:

char c = 'A';
for (int i = 0; i < CHAR_BIT; i++) printf("%d", ((c << i) & 128) ? 1 : 0);
Nahoru Odpovědět
6.9.2019 12:55
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Odpovídá na DarkCoder
Patrik Pastor:6.9.2019 13:09

to uz mi funguje. Jaky je ale ten vyznacny rozdil? kdyz to porovnavas 10000000 (128) a davas ternani operator nebo bez neho. Jak to, ze bez ternarniho operatoru, nedostanu takovy vysledek, kdyz (c<<i) & 128 taky vraci '1' nebo '0' (true/false). Neni potom ten ternarni operator zbytecny?

 
Nahoru Odpovědět
6.9.2019 13:09
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:6.9.2019 13:26

Rozdíl je ve vrácené hodnotě. Výsledek X & 128 bude buď 0 nebo 128, výsledek X & 1 bude buď 0 nebo 1. Ternární operátor mi vrací 1 pokud je hodnota bitového součinu nenulová. Nenulová hodnota vznikne pouze tehdy, když bity obou operandů jsou jedničkové. A abych nevypisoval hodnotu 128 ale vypisoval hodnotu 1, je použit ternární operátor který opravuje nenulovou hodnotu bitového součinu na 1.

Nahoru Odpovědět
6.9.2019 13:26
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 18 zpráv z 18.