Source: https://www.cs.uky.edu/~raphael/programming.html
Studenter i CS-klassene på junior- og seniornivå forventes å kunne litt bakgrunnsinformasjon om C , Unix og programvareutviklingsverktøy. Denne siden beskriver noe av denne bakgrunnen og foreslår noen øvelser.
C
- C lar deg deklarere variabler utenfor enhver prosedyre. Disse variablene kalles globale variabler.
- En global variabel tildeles én gang når programmet starter og blir værende i minnet til programmet avsluttes.
- En global variabel er synlig for alle prosedyrer i samme fil.
- Du kan gjøre en global variabel deklarert i filen Ac synlig for alle prosedyrer i noen andre filer Bc, Cc, Dc, … ved å erklære den med modifikatoren ekstern i filene Bc, Cc, Dc, … som i dette eksemplet:
ekstern int theVariable
Hvis du har mange filer som deler variabelen, bør du erklære den ekstern i en header-fil foo.h (beskrevet nedenfor ) og bruke #include foo.h i filene Bc, Cc, Dc, … . Du må deklarere variabelen i nøyaktig én fil uten den eksterne modifikatoren, ellers blir den aldri allokert i det hele tatt. - Det er ikke en god idé å bruke for mange globale variabler, fordi du ikke kan lokalisere stedene der de er tilgjengelige. Men det er situasjoner der globale variabler lar deg unngå å overføre mange parametere til funksjoner.
- Strenger er pekere til nullterminerte tegnmatriser.
- Du erklærer en streng som char *variableName .
- Hvis strengen din er konstant, kan du bare tilordne en streng bokstavelig til den:
char *myString = “Dette er en prøvestreng”; - En tom streng har bare nullterminatoren:
myString = “”; // tom streng, lengde 0, som inneholder null
En null-peker er ikke en gyldig strengverdi:
myString = NULL; // ugyldig streng
Du kan bruke en slik verdi for å indikere slutten av en rekke strenger:
argv[0] = “programnavn”;
argv[1] = “firstParam”;
argv[2] = “secondParam”;
argv[3] = NULL; // terminator
Hvis strengen din beregnes under kjøring, må du reservere nok plass til å holde den. Rommet må være nok til å holde null på slutten:
char *myString;
myString = (char *) malloc(strlen(someString) + 1); // tildele plass
strcpy(myString, someString); // kopier someString til myString
- For å unngå minnelekkasjer bør du etter hvert returnere plass som du tildeler med malloc ved å bruke gratis . Parameteren må være begynnelsen på plassen som returneres av malloc :
free((void *) myString);
For å holde koden ren og lesbar, bør du ringe free() i samme prosedyre som du kaller malloc() ; du kan kalle andre prosedyrer mellom disse to punktene for å manipulere strengen din. - Hvis du kopierer strenger, bør du være veldig forsiktig med å aldri kopiere flere byte enn måldatastrukturen kan inneholde. Bufferoverløp er den vanligste årsaken til sikkerhetsfeil i programmer. Vurder spesielt å bruke strncpy() og strncat() i stedet for strcpy() og strcat() .
Hvis du bruker C++, må du konvertere strengobjektene dine til C-stilstrenger før du sender dem i et systemanrop.
string myString // Denne erklæringen fungerer bare i C++
…
someCall(myString.c_str())
Dessverre returnerer c_str() en uforanderlig streng. Hvis du trenger en mutbar streng, kan du enten kopiere dataene ved å bruke strcpy() (som ovenfor), eller du kan caste typen:
someCall(const_cast<char *>(myString.c_str()))
Casting er ikke like trygt som å kopiere, fordi someCall() faktisk kan endre strengen, noe som vil forvirre enhver del av programmet som antar at myString er konstant, som er den vanlige oppførselen til C++-strenger.
- En buffer er et minneområde som fungerer som en beholder for data. Selv om dataene kan ha en tolkning (for eksempel en rekke strukturer med mange felt), behandler programmer som leser og skriver buffere dem ofte som matriser med byte. En rekke byte er ikke det samme som en streng, selv om de begge er erklært char * eller char [] .
- De inneholder kanskje ikke ASCII-tegn, og de kan ikke være null-terminerte.
- Du kan ikke bruke strlen() for å finne lengden på data i en buffer (fordi bufferen kan inneholde nullbyte). I stedet må du finne ut lengden på data med returverdien fra systemanropet (vanligvis lest ) som genererte dataene.
- Du kan ikke bruke strcpy() , strcat() , eller relaterte rutiner på bytebuffere; i stedet må du bruke memcpy() eller bcopy() .
Du skriver en buffer på 123 byte til en fil ved å bruke kode som dette:
char *filnavn = “/tmp/foo”
#define BUFSIZE 4096
røye buff[BUFSIZE]; // buffer som inneholder maksimalt BUFSIZE byte
…
int utfil; // filbeskrivelse, et lite heltall
int bytesToWrite; // antall byte som fortsatt skal skrives
char *outPtr = buf;
…
if ((outFile = creat(filnavn, 0660)) < 0) { // feil
// se filtillatelser for å forstå 0660
perror(filnavn); // skrive ut årsak
exit(1); // og gå ut
}
bytesToWrite = 123; // initialisering; 123 er bare et eksempel
while ((bytesWritten = write(outFile, outPtr, bytesToWrite)) < bytesToWrite) {
// ikke alle byte er skrevet ennå
if (bytesWritten < 0) {// failure
perror(“skriv”);
exit(1);
}
outPtr += bytesSkrevet;
bytesToWrite -= bytesWritten;
}
For å få kompilatoren til å allokere plass til buffere, må du deklarere bufferen med en størrelse som kompilatoren kan beregne, som i
#define BUFSIZE 1024
- røye buff[BUFSIZE];
Hvis du bare erklærer bufferen uten størrelse:
røyebuff[];
da har den ukjent størrelse og C tildeler ingen plass. Det er akseptabelt hvis buf er en formell parameter (det vil si at den vises i en prosedyreoverskrift); den faktiske parameteren (oppgitt av den som ringer) har en størrelse. Men det er ikke akseptabelt hvis buf er en variabel. Hvis du ikke vet størrelsen på bufferen på kompileringstidspunktet, bør du bruke kode som dette:
char *buf = (char *) malloc(bufferStørrelse);
hvor bufferSize er kjøretidsresultatet av en eller annen beregning.
- Du kan dynamisk tildele og deallokere minne.
Individuelle forekomster av alle typer:
typedef … myType;
myType *myVariable = (minType *) malloc(størrelse på(minType));
// du kan nå få tilgang til *myVariable.
…
free((void *) myVariable);
Igjen, det er god programmeringspraksis å påkalle free() i samme rutine som du kaller malloc() .
Endimensjonale arrays av enhver type:
myType *myArray = (myType *) malloc(arrayLength * sizeof(myType));
// myArray[0] .. myArray[arrayLength – 1] er nå tildelt.
…
free((void *) myArray);
Todimensjonale matriser er representert av en rekke pekere, som hver peker til en matrise:
myType **myArray = (myType **) malloc(antallRows * sizeof(myType *));
int rowIndex;
for (rowIndex = 0; rowIndex < numRows; rowIndex += 1) {
myArray[rowIndex] = (minType *) malloc(antallKolonner * størrelsen på(minType));
}
// myArray[0][0] .. myArray[0][numColumns-1] .. myArray[antallRows-1][numColumns-1]
// er nå tildelt. Du vil kanskje initialisere dem.
…
for (rowIndex = 0; rowIndex < numRows; rowIndex += 1) {
free((void *) myArray[rowIndex]);
}
free((void *) myArray);
Hvis du bruker C++, ikke bland ny/slett med malloc/free for samme datastruktur. Fordelen med ny/slett for klasseforekomster er at de automatisk kaller konstruktører, som kan initialisere data, og destruktorer, som kan sluttføre data. Når du bruker malloc/free , må du eksplisitt initialisere og fullføre.
- Heltall
- C representerer vanligvis heltall i 4 byte. For eksempel er tallet 254235 representert som det binære tallet 00000000,00000011,11100001,00011011.
- På den annen side representerer ASCII-tekst tall som alle andre tegn, med en byte per siffer ved bruk av en standardkoding. I ASCII er tallet 254235 representert som 00110010, 00110101, 00110110, 00110010, 00110011, 00110101.
- Hvis du trenger å skrive en fil med heltall, er det generelt mer effektivt både i rom og tid å skrive 4-byte-versjonene enn å konvertere dem til ASCII-strenger og skrive disse. Slik skriver du et enkelt heltall til en åpen fil:
write(outFile, &myInteger, sizeof(myinteger))
Du kan se på de individuelle bytene til et heltall ved å kaste det som en struktur på fire byte:
int IP-adresse; // lagret som et heltall, forstått som 4 byte
typedef struct {
char byte1, byte2, byte3, byte4;
} IPDetails_t;
IPDetails_t *detaljer = (IPDetails_t *) (&IP-adresse);
printf(“byte 1 er %o, byte 2 er %o, byte 3 er %o, byte 4 er %o\n”,
detaljer->byte1, detaljer->byte2, detaljer->byte3, detaljer->byte4);
Multi-byte heltall kan representeres forskjellig på forskjellige maskiner. Noen (som Sun SparcStation) setter den viktigste byten først; andre (som Intel i80x86 og dens etterkommere) setter den minst signifikante byten først. Hvis du skriver heltallsdata som kan leses på andre maskiner, konverter dataene til “nettverks”-byte-rekkefølge etter htons() eller htonl() . Hvis du leser heltallsdata som kan ha blitt skrevet på andre maskiner, konverter dataene fra “nettverks”-rekkefølgen til din lokale byte-rekkefølge med ntohs() eller ntohl() .
Du kan forutsi minneoppsettet til strukturer og verdien som sizeof() vil returnere. For eksempel
struct foo {
røye a; // bruker 1 byte
// C setter inn en 3-byte pute her slik at b kan starte på en 4-byte grense
int b; // bruker 4 byte
usignert kort c; // bruker 2 byte
usignert char d[2]; // bruker 2 byte
};
Derfor returnerer sizeof(struct foo ) 12. Denne forutsigbarheten (for en gitt arkitektur) er grunnen til at noen kaller C et “bærbart assemblerspråk”. Du må forutsi strukturoppsett når du genererer data som må følge et spesifikt format, for eksempel en overskrift på en nettverkspakke.
- Du kan deklarere pekere i C til enhver type og tilordne dem verdier som peker til objekter av den typen.
Spesielt lar C deg bygge pekere til heltall:
int noeHeltal;
int *intPtr = &noenHeltall; // erklærer en variabel med pekerverdi og tildeler en passende pekerverdi
noenCall(intPtr); // sender en peker som en faktisk parameter
- noenCall(&noeHeltall); // har samme effekt som ovenfor
- AC-biblioteksprosedyre som tar en peker til en verdi, endrer mest sannsynlig denne verdien (den blir en “ut” eller en “inn ut”-parameter). I eksemplet ovenfor er det svært sannsynlig at someCall endrer verdien av heltall someInteger .
Du kan bygge en peker til en rekke heltall og bruke den til å gå gjennom den matrisen.
#define ARRAY_LENGTH 100
int intArray[ARRAY_LENGTH];
int *intArrayPtr;
…
int sum = 0;
for (intArrayPtr = intArray; intArrayPtr < intArray+ARRAY_LENGTH; intArrayPtr += 1) {
sum += *intArrayPtr;
}
Du kan bygge en peker til en rekke strukturer og bruke den til å gå gjennom den matrisen.
#define ARRAY_LENGTH 100
typedef struct {int foo, bar;} par_t; // pair_t er en ny type
pair_t structArray[ARRAY_LENGTH]; // structArray er en matrise med ARRAY_LENGTH pair_t-elementer
pair_t *structArrayPtr; // structArrayPtr peker på et pair_t-element
…
int sum = 0;
for (structArrayPtr = structArray; structArrayPtr < structArray+ARRAY_LENGTH; structArrayPtr += 1) {
sum += structArrayPtr->foo + structArrayPtr->bar;
}
- Når du legger til et heltall til en peker, blir pekeren avansert med så mange elementer, uansett hvor store elementene er. Kompilatoren vet størrelsen og gjør det rette.
- Produksjon
- Du formaterer utdata med printf eller dens variant, fprintf .
- Formatstrengen bruker %d , %s , %f for å indikere at et heltall, streng eller reell skal plasseres i utdataene.
- Formatstrengen bruker \t og \n for å indikere tabulator og ny linje.
- Eksempel:
printf(“Jeg tror at tallet %d er %s\n”, 13, “heldig”); - Blanding av printf() , fprintf() og cout kan kanskje ikke skrive ut elementer i den rekkefølgen du forventer. De bruker uavhengige oppsamlingsområder (“buffere”) som de skriver ut når de er fulle.
- Main()-rutinen tar funksjonsparametere som representerer kommandolinjeparametere.
- En vanlig måte å skrive hovedrutinen på er denne:
int main(int argc; char *argv[]);
Her er argc antall parametere, og argv er en rekke strenger, det vil si en rekke pekere til nullterminerte tegnarrayer.
- En vanlig måte å skrive hovedrutinen på er denne:
Etter konvensjon er det første elementet i argv navnet på selve programmet.
int main(int argc; char *argv[]);
printf(“Jeg har %d parametere; mitt navn er %s, og min første parameter er %s\n”,
argc, argv[0], argv[1]);
- Praktiske språkfunksjoner
- Du kan øke et heltall eller ha en peker til neste objekt ved å bruke ++- operatoren. Det er vanligvis best å plassere denne operatoren etter variabelen: myInt++ . Hvis du setter ++ foran variabelen, økes variabelen før den evalueres, noe som sjelden er det du ønsker.
Du kan bygge en oppgave der venstresidevariabelen deltar som første del av uttrykket på høyre side:
myInt -= 3; // tilsvarende myInt = myInt – 3
myInt *= 42; // tilsvarende myInt = myInt * 42
- myInt += 1; // tilsvarende og kanskje å foretrekke til myInt++
- Du kan uttrykke tall i desimal, oktal (ved å sette prefiks med sifferet 0 , som i 0453 ), eller hex (ved å sette prefiks med 0x , som i 0xffaa ).
Du kan behandle et heltall som et sett med biter og utføre bitvise operasjoner:
myInt = myInt | 0444; // bitvis ELLER; 0444 er i oktal
myInt &= 0444; // bitvis OG med en oppgavestenografi
myInt = noe ^ uansett; // bitvis XOR
C og C++ har betingede uttrykk. I stedet for å skrive
hvis (a < 7)
a = noen Verdi
ellers
a = en annenVerdi;
du kan skrive
a = a < 7 ? someValue : someOtherValue;
- Tildelinger returnerer verdien til venstre side, slik at du kan inkludere en tilordning i større uttrykk, for eksempel betingelser. Men du bør følge konvensjonen om at slike oppgaver alltid er omgitt av parenteser for å indikere både for noen som leser koden din og for kompilatoren at du egentlig mener en oppgave, ikke en likhetstest. Skriv for eksempel
if ((s = socket(…)) == -1)
ikke
if (s = socket(…) == -1)
Den andre versjonen er både vanskeligere å lese og, i dette tilfellet, feil, fordi likhetsoperatøren == har høyere prioritet enn oppgaveoperatøren = .
- Programmer som ikke er trivielt korte, bør vanligvis dekomponeres i flere kildefiler , hver med et navn som slutter på .c (for C-programmer) eller .cpp (for C++-programmer).
- Prøv å gruppere funksjoner som manipulerer de samme datastrukturene eller har relaterte formål i samme fil.
- Alle typer, funksjoner, globale variabler og manifestkonstanter som er nødvendig for mer enn én kildefil, bør også deklareres i en overskriftsfil , med et navn som slutter på .h .
- Med unntak av innebygde funksjoner, ikke deklarer funksjonskropper (eller noe som får kompilatoren til å generere kode eller tildele plass) i overskriftsfilen.
- Hver kildefil skal referere til de overskriftsfilene den trenger med en #include -linje.
- Aldri #inkluder en .c- fil.
- Når du har flere kildefiler, må du koble sammen alle de kompilerte objektfilene sammen med eventuelle biblioteker som programmet ditt trenger.
- Den enkleste metoden er å bruke C-kompilatoren, som vet om C-bibliotekene:
gcc *.o -o mittProgram
Denne kommandoen ber kompilatoren koble alle objektfilene med C-biblioteket (som implisitt er inkludert) og plassere resultatet i filen myProgram , som blir kjørbar. - Hvis programmet ditt trenger andre biblioteker, bør du spesifisere dem etter objektfilene dine, fordi linkeren bare samler rutiner fra biblioteker som den allerede vet at den krever, og den kobler sammen filer i den rekkefølgen du angir. Så hvis du trenger et bibliotek som libxml2 , bør koblingskommandoen din være omtrent slik:
gcc *.o -lxml2 -o myProgram
Kompilatoren vet hvordan man søker i forskjellige standardkataloger for den gjeldende versjonen av libxml2 .
- Den enkleste metoden er å bruke C-kompilatoren, som vet om C-bibliotekene:
- Feilsøking av C-programmer
- Hvis du får en segmenteringsfeil, har du mest sannsynlig en indeks utenfor rekkevidde, en uinitialisert peker eller en null-peker.
- Du kan sette utskriftssetninger i programmet for å hjelpe deg med å lokalisere en feil.
- Feilsøking vil sannsynligvis være mest vellykket hvis du bruker gdb (beskrevet nedenfor ) for å finne ut hvor feilen er.
- Programmer som kjører over lang tid må være forsiktige med å frigjøre alt minne de tildeler, ellers går de tom for minne. For å feilsøke minnelekkasjer kan du vurdere disse artiklene om feilsøking av C-minnelekkasjer og C++-minnelekkasjer .
Unix
- Etter konvensjon starter hver prosess med tre standardfiler åpne: standard input, standard output og standard error, assosiert med filbeskrivelser 0, 1 og 2.
- Standardinngang er vanligvis koblet til tastaturet. Uansett hva du skriver går til programmet.
- Standard utgang er vanligvis koblet til skjermen din. Uansett programutganger blir synlige.
- Standardfeil er også vanligvis koblet til skjermen din.
- Du kan bruke skallet til å starte programmer slik at standardutgangen til ett program er direkte koblet (“piped”) til standardinngangen til et annet program:
ls | wc
Du kan bruke skallet til å starte programmer slik at standardinndata og/eller utdata er koblet til en fil:
ls > lsOutFile
wc < lsOutFile
sort -u < largeFile > sortedFile
- Generelt vet eller bryr ikke programmer seg om skallet har omorganisert betydningen av standardfilene deres.
- Unix-kommandoer
- Kommandoer er bare navnene på kjørbare filer. PATH – miljøvariabelen forteller skallet hvor de skal lete etter dem. Vanligvis har denne variabelen en verdi som /bin:/usr/bin:/usr/local/bin:. .
- For å se hvor skallet finner et bestemt program, for eksempel vim , si hvor vim .
- Systemsamtaler og biblioteksamtaler følger noen viktige konvensjoner.
- Returverdien til anropet indikerer vanligvis om anropet lyktes (vanligvis verdien er 0 eller positiv) eller mislyktes (vanligvis er verdien -1).
Sjekk alltid returverdien til biblioteksamtaler. Når et systemkall mislykkes, kan perror() -funksjonen skrive ut hva feilen var (til standardfeil):
int fd;
char *filnavn = “minfil”;
if ((fd = åpen(filnavn, O_RDONLY)) < 0) {
perror(filnavn); // kan skrive ut “min fil: Ingen slik fil eller katalog”
}
- En manuell side for et systemanrop eller en bibliotekrutine kan vise en datatype som den ikke definerer, for eksempel size_t eller time_t eller O_RDONLY . Disse typene er vanligvis definert i overskriftsfiler nevnt på manualsiden; du må inkludere alle disse overskriftsfilene i C-programmet.
- Filtillatelser i Unix uttrykkes vanligvis med oktale tall.
- I eksemplet med creat() ovenfor, er 0660 et oktalt tall (det er det ledende 0 betyr), som representerer binær 110 110 000. Dette oktaltallet gir lese- og skrivetillatelser, men ikke kjøringstillatelser, til filens eier og filens gruppe, men ingen tillatelser til andre brukere.
- Du angir tillatelser når du oppretter en fil med parameteren til creat() -kallet.
- Kommandoen ls -l viser deg tillatelser til filer.
- Du kan endre tillatelsene til en fil du eier ved å bruke chmod -programmet.
- Alle prosessene dine har en karakteristikk kalt umask, vanligvis representert som et oktalt tall. Når en prosess oppretter en fil, fjernes bitene i umasken fra tillatelsene spesifisert i creat() -kallet. Så hvis umasken din er 066, kan ikke andre lese eller skrive filer du oppretter, fordi 066 representerer lese- og skrivetillatelser for gruppen din og for andre personer. Du kan inspisere og endre umasken din ved å bruke umask -programmet, som du vanligvis påkaller i oppstartsskriptet for skallet ditt (avhengig av skallet ditt, ~/.login eller ~/.profile ).
Programvareutviklingsverktøy
- Bruk et tekstredigeringsprogram til å lage, endre og inspisere programmet ditt. Det er flere rimelige tekstredigerere tilgjengelig.
- Vim – redigeringsprogrammet og dets grafiske grensesnitt, gvim , krever litt innsats for å lære, men de gir et sett med verktøy av veldig høy kvalitet for redigering av programfiler, inkludert syntaksutheving, parentesmatching, ordfullføring, automatisk innrykk, søk etter tag (som flytter deg raskt fra et sted hvor programmet kaller en funksjon til stedet der funksjonen er definert) og integrert søk. Vim er designet for tastaturbruk; du trenger aldri å bruke musen hvis du ikke vil. Den er fritt tilgjengelig for Unix, Win32 og Microsoft operativsystemer. Det er den mest utviklede versjonen av editor-serien som inkluderer ed , ex , vi og elvis . Du kan lese nettdokumentasjon for vim og få umiddelbar hjelp gjennom vims :help – kommando.
- Emacs – editoren er, om noe, mer funksjonsfylt enn vim . Det krever også betydelig innsats å lære. Den er også fritt tilgjengelig for både Unix og Microsoft operativsystemer. Du finner dokumentasjon her .
- Det er mange andre tekstredigerere tilgjengelig, men generelt gir de deg ikke de to mest nyttige funksjonene du trenger for å lage programmer: automatisk innrykk og syntaksutheving. Imidlertid har disse tekstredigererne ofte fordelen av å være lettere å lære, i tråd med deres begrensede evner. Blant disse tekstredigerere av lavere kvalitet er (for Unix) pico , gedit og joe og (for Microsoft) notepad og word .
- Du er kanskje kjent med et integrert utviklingsmiljø (IDE) som Eclipse, Code Warrior eller .NET. Disse miljøene har generelt tekstredigerere som er integrert med debuggere og kompilatorer. Hvis du bruker en slik IDE, er det fornuftig å bruke de tilknyttede tekstredigererne.
- gdb er en debugger som forstår dine variabler og programstruktur.
- Du finner dokumentasjon her .
- For å bruke gdb effektivt, må du sende flagget -g til C- eller C++-kompilatoren.
- Hvis programmet myProgram mislyktes med å forlate en fil kalt core , så prøv gdb myProgram core .
- Du kan også kjøre programmet fra begynnelsen under kontroll av gdb : gdb myProgram .
- Alle kommandoer til gdb kan forkortes til et unikt prefiks.
- Hjelpekommandoen er veldig nyttig .
- Where – kommandoen viser anropsstakken, inkludert linjenumre som viser hvor hver rutine er. Dette er den første kommandoen du bør prøve når du feilsøker en kjernefil.
- For å skrive ut verdien av et uttrykk (du kan inkludere variablene og de vanlige C-operatorene), skriv print expression , som i
print (myInt + 59) & 0444; - For å se programmet ditt, prøv list myFunction eller list myFile.c:38 .
- For å angi en annen aktiveringspost som gjeldende, bruk opp (for nyere) eller ned (for mindre nylig) kommandoen.
- Du kan angi et bruddpunkt på hvilken som helst linje i en fil. For eksempel kan du si break foo.p:38 for å sette et bruddpunkt på linje 38 i filen foo.p . Hver gang programmet ditt treffer den linjen mens det kjører, vil det stoppe og gdb vil be deg om kommandoer. Du kan for eksempel se på variabler eller gå videre gjennom programmet.
- Den neste kommandoen går frem en setning (ringer og returnerer fra alle prosedyrer om nødvendig).
- Step – kommandoen går en setning frem, men hvis setningen involverer et prosedyrekall, går den inn i prosedyren og stopper ved den første setningen der.
- Hvis du skriver inn kommandosettet follow-fork-mode child , vil gdb fortsette å feilsøke barnet, ikke forelderen når programmet kjører fork() -kallet.
- Forlat gdb ved å skrive inn quit -kommandoen.
- Du foretrekker kanskje å bruke ddd grafiske grensesnitt til gdb .
- Gi alltid kompilatorprogrammer gcc eller g++ -Wall – flagget for å slå på et høyt nivå av advarsler. På samme måte gi javac flagget -Xlint:all . Ikke lever inn et program som genererer kompileringstidsvarsel.
- Du kan lese manualen for å få detaljer om programmer, C-biblioteksrutiner og Unix-systemanrop ved å bruke man- programmet, som i man printf eller man gcc .
- Noen ganger finnes funksjonen du ønsker i en bestemt del av Unix-manualen, og du må eksplisitt be om den: man 2 open eller man 3 printf . Seksjon 1 dekker programmer, seksjon 2 dekker systemanrop, og seksjon 3 dekker C-biblioteket, og seksjon 8 dekker systemadministrasjon. Du trenger mest sannsynlig ikke de andre delene.
- Du kan finne ut om et program, C-biblioteksrutine eller Unix-systemanrop er relevant for et emne ved å bruke -k- flagget, som i man -k print .
- Bruk make -programmet til å organisere oppskrifter for rekompilering og rekobling av programmet når du endrer en kildefil.
- Se denne veiledningen eller denne håndboken for detaljer.
Hvis programmet ditt er sammensatt av flere filer, kan du kompilere dem separat og deretter koble dem sammen. Du kompilerer med flagget -c , og bruker flagget -o for å indikere utdatafilen. En rimelig makefil kan se slik ut:
KILDER = driver.c input.c output.c
OBJEKTER = driver.o input.o output.o
HEADERS = vanlig.h
CFLAGS = -g -Vegg
program: $(OBJEKTER)
$(CC) $(CFLAGS) $(OBJECTS) -o program
$(OBJECTS): $(HEADERS)
testRun: program
program < testData
Denne makefilen bruker en innebygd definisjon av CC og en innebygd regel for å konvertere C-kildefiler som driver.c til objektfilen deres. Hvis du endrer bare input.c , så vil make testRun få kompilatoren til å gjenoppbygge input.o , deretter få kompilatoren til å koble objektene på nytt, lage program og deretter kjøre programmet med standard input omdirigert fra filen testData .
- Hvis du har mange kildefiler og mange overskriftsfiler, kan det være lurt å bruke makedepend- programmet til automatisk å bygge Makefile -reglene som spesifiserer hvordan kildefiler avhenger av overskriftsfiler. Eksemplet ovenfor antar at alle kildefiler avhenger av alle overskriftsfiler, noe som ofte ikke er tilfelle.
- grep – programmet kan raskt søke etter en definisjon eller variabel, spesielt i include-filer:
grep “struct timeval {” /usr/include/*/*.h
Øvelser
Gjør disse øvelsene i C.
- Skriv et program kalt atoi som åpner en datafil navngitt på kommandolinjen og leser fra den en enkelt inngangslinje, som skal inneholde et heltall representert med ASCII-tegn. Programmet konverterer den strengen til et heltall, multipliserer heltallet med 3 og skriver ut resultatet til standard ut. Programmet må ikke bruke atoi() -funksjonen. Du bør bruke make -programmet. Makefilen din bør ha tre regler: atoi , run (som kjører programmet på standard testdata og omdirigerer utdataene til en ny fil) og clean (som fjerner midlertidige filer). Sørg for at programmet kjører riktig på dårlige data og avslutter med en nyttig melding hvis datafilen mangler eller er uleselig. Gå gjennom programmet ved å starte det med gdb , plassere et bruddpunkt på main() , og bruke trinn- kommandoen gjentatte ganger.
- Slå opp manualsiden for katteprogrammet . Kod din egen versjon av cat . Versjonen din må godta flere (eller ingen) filnavnparametere. Den trenger ikke godta noen alternativparametere.
Skriv et program removeSuffix som tar en enkelt parameter: navnet på en suffiksfil. Suffiksfilen har én linje per oppføring. En oppføring er en ikke-tom streng, som vi kaller et suffiks , etterfulgt av tegnet >, etterfulgt av en annen (eventuelt tom) streng, som vi kaller en erstatning . Programmet ditt skal lagre alle suffiksene og deres erstatninger i en hash-tabell. Bruk ekstern kjetting. Programmet ditt skal da lese standardinndata. For hvert mellomromsavgrensede ord w i inndata, finn det lengste suffikset s som vises i w , og modifiser w ved å fjerne s og sette inn s -erstatningen, og skape w’ . Skriv ut én linje per modifisert ord, i formen w>w’ . Ikke skriv ut ord som ikke er endret.