1. Homepage of Dr. Zoltán Porkoláb
    1. Home
    2. Archive
  2. Teaching
    1. Timetable
    2. Imperative programming (BSc)
    3. Multiparadigm programming (MSc)
    4. C programming (BSc for physicists)
    5. Project tools (BSc)
    6. Bolyai College
    7. C++ (for foreign studenst)
    8. Software technology lab
    9. BSc and MSc thesis
  3. Research
    1. Templight
    2. CodeChecker
    3. CodeCompass
    4. Projects
    5. Publications (up to 2011)
    6. PhD students
  4. Affiliations
    1. Dept. of Programming Languages and Compilers
    2. Ericsson Hungary Ltd

Imperatív programozás 10.

Típusabsztrakció

Komponensek

Nagy programok esetében nem reális, hogy az elejétől a végéig minden részét mi írjuk meg. Ez részben igen sok munkaóra lenne (ne csak a fejlesztési időt, hanem a tesztelést, és a későbbi karbantartást is vegyük figyelembe), részben pedig felesleges is: nagy valószínűséggel számos komponensre másoknak is szüksége volt már, és ezek a komponensek léteznek.

A többszöri újrafelhasználásra szánt szoftverkomponenseket könyvtárak-nak (library) nevezzük. A legtöbb programozási nyelv rendelkezik egy olyan szabványos könyvtárral (standard library), ami a legalapvetőbb funkcionalitást biztosítja: input/output, stringműveletek, alapvető matematikai műveletek, stb. Ezen felül egyes nyelvek számos további szolgáltatást biztosíthatnak: a C++ STL szabványos adatszerkezeteket és rajtuk végrehajtható általános algoritmusokat szolgáltat, a Java nagyszámoú könyvtára között pedig grafikus interfészt, adatbázis- és hálózatkezelést és rengeteg más komponenst találunk. A C nyelv szabványos könyvtára a kisebbek közé tartozik.

A könyvtárak megjelenési formája gyakran a nyelv által támogatott programozási paradigmától függ: Java-ban, C#-ban gyakran osztálykönyvtárakként valósulnak meg, C++-ban sokszor template osztályok és függvények. A procedurális C-ben, többnyire függvénykönyvtárakat találunk. Azt a kódot, amelyik felhasználja a könyvtárat, felhasználói, vagy kliens kódnak nevezzük.

Interfész és implementáció

Egy (akár hardver- akár szoftver-) komponens pontosan meghatározott módon kell kommunikáljon az őt használó többi komponenssel. Fontos, hogy ezen a felületen kívül ne lehessen elérni a komponens belsejét. Ezt a felületet a komponens interfészének (interface) nevezzük. Az interfész egyfajta szerződés a komponens (létrehozója) és az azt használók (szoftverek) között: a komponens ezen keresztül garantál egy funkcionalitást, amire a felhasználók (szoftverek) építhetnek. Másrészről, mivel a kliensek csak ezen a felületen keresztül érhetik el a szolgáltatásokat, azok megvalósítása, azaz az implementáció (implementation) teljes egészében a komponens megvalósítójának kezében van, azon szükség esetén (bizonyos kereteken belül) módosíthat is.

Az interfész sokféle összetevőből állhat: konstansok, adattípusok, függvények, kivételek akár még változók is. Az alábbi példában egy dátum komponens látható.

1
2
3
4
5
6
7
8
9
10
11
12
13
struct date
{
  int year;
  int month;
  int day;   
};

void f(void)
{
  struct date exam = { 2018, 12, 17}; /* mezőnkénti inicializálás  */
  ++exam.day;                         /* egy nappal elhalasztva    */
  exam.day += 40;                     /* valószínűleg hiba         */
}

Ha van valamilyen komponens szintű állandó, akkor azt nyugodtan definiálhatjuk csak olvasható (constant, immutable) változónak. Amennyiben aznban írható/olvasható változóink vagy adattagjaink vannak, akkor nehéz megakadályozni, hogy a kliens hibás adatot (január 42-e, vagy február 30-a) állítson be. Ezért gyakoribb, hogy változók helyett alprogramok-at (függvény, metódus, property) használunk.

1
2
3
4
5
6
7
8
9
10
11
12
13
struct date
{
  int year;
  int month;
  int day;   
};

void f(void)
{
  struct date exam = { 2018, 12, 17}; /* mezőnkénti inicializálás  */
  nextDate(&exam);                    /* egy nappal elhalasztva    */
  addDate(&exam,40);                  /* lekezeli a hónap végét    */
}

Egy eljárás, mint az addDate() ellenőrizni tudja a paramétereit, illetve a dátum szemantikájának megfelelően hajtja végre a növelést, pl növeli a hónap vagy év értékét is. A fent C példában azért adtuk át az interfész függvényeknek a dátum objektum címét, mert csak így tudjuk módosítani magát az objektumot.

A klienssel közölni kell, hogy milyen módon tudja használni a könyvtárat. Ez aalpján a fordítóprogram képes (valamilyen szinten) ellenőrizni ezt a használatot és jelenzi, ha valamol eltértünk a megfelelő módtól. Ez java-ban az import, C#-ban a use utasításal történik. C-ben és C++-ban a felhasználni kívánt könyvtár headerének include-ja segítségével tesszük meg.

Megjegyzések

  1. Példa az implementáció/interfész szétválasztására: A UNIX operációs rendszert pl. csak a megfelelő rendszerhívásokon keresztül lehet elérni. Ez egy hosszútávon stabil felület, akár 20+ évvelezelőtt megírt rendszerprogramok is lefordulnak és futnak. Másrészt a rendszerhívások mögött a javítások, fejlesztések folyamatosak lehetnek.

  2. A C és C++ nyelvekben egy komoly korlát lehet a bináris kmpatibilitás megőrzése, amire akkor van szükség, ha dinamikus szerkesztésű programok esetén megváltoztatjuk a könyvtárat. Ennek az oka a C és C++ nyelvek érték szemantikája, azaz a kliensnek pl. tudnia kell, hány bájt méretű egy változó vagy függvényparaméter. ha pl. a fenti programban megváltozik a struct date struktúra szerkezete, akkor nem csak a dátum komponenst, hanem a dátumot felhasználó kliens programokat is újra kell fordítani.

Egységbezárás

A fenti dátum példához megadhatunk egy teljes értékű interfészt, ami alapján a kliens programok a teljes funkcionalitást fel tudják használni. De hogyan tudjuk megakadályozni, hogy a kliens az interfészt megkerülve közvetlenül elérje az implementációt?

Azokat a programnyelvi mehanizmusokat, amelyek az implementáció elérését megakadályozzák és rákényszeríti a klienst az interfész felhasználására kényszeríti, egységbezárás-nak (enkapszuláció, encapsulation, information hiding) nevezzük.

A különböző programozási paradigmák eltérő eszközöket adnak és az egyes nyelvek is eltérő mélységben támogatják ezeket. Az objektum-orientált nyelvekben pl. az adattagok és metódusok elérési jogai (access modifiers)
(pl. public, protected, private) teszik lehetővé az információ elrejtését. A class private vagy protected neveit nem érhetjük el az osztályon kívülről, az fordítási időben szintaktikus hibát okoz.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class date
{
public:
  void set(int y, int m, int d);
  void next();
  void add(int n);
  int  getYear() const;
  int  getMonth() const;
  int  getday() const;
private:
  int year;
  int month;
  int day;   
};

void f(void)
{
  date exam;
  exam.set(2018,12,17);        /* inicializálás                */
  exam.next()  ;               /* egy nappal elhalasztva       */
  exam.add(40);                /* lekezeli a hónap végét       */
  cout << exam.getDay();       /* ok                           */
  exam.day += 40;              /* syntax error: day is private */
}

Enkapszuláció C-ben

Az alábbi példa egy dinamikusan növekvő vektor típus megvalósítása, hasonló, mint ami a C++ standard könyvtár std::vector típusa. Az interfészt jelentősen leegyszerűstettük az std::vector-hoz képest.

  1. Létre tudunk hozni egy üres vektort (vectorCreate)
  2. Be tudunk szúrni egy új elemet a vektor végére (vectorPushBack)
  3. Törölhetjük a vektor utolsó elemét (vectorPopBack)
  4. Írhatjuk a vekror i-edik elemét, ha i kisebb, mint a méret (vectorSet)
  5. Olvashatjuk a vekror i-edik elemét, ha i kisebb, mint a méret (vectorGet)
  6. Lekérdezhetjük a vektor elemeinek számát (vectorSize)
  7. Lekérdezetjük az implementációs buffer méretét (vectorCapacity)
  8. Törölhetjük a vektort, annak tartalmával együtt (vectorDestroy)
  9. Átmásolhatjuk a vektor tartalmát egy másik vektorba (vectorCopy)
  10. Átmozgathatjuk a vektor adatait, a régi vektor üres lesz (vectorMove)

A hibaüzeneteket a vectorErrno globális változóból tudjuk kiolvasni.

A felhasználó például így szeretné használni a vector könyvtárat:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include "vector.h"

int main()
{
  int i;                                        /* így nézne ki C++-ban */  
  vector_t vec1 = vectorCreate();               /*    vector_t vec1;    */
  vector_t vec2 = vectorCreate(); 
  vector_t vec3 = vectorCreate();   

  printf( "vec1 size = %u\n", vectorSize(vec1));   /* vec1.size()       */

  for(i = 0; i < 100; ++i)
  {
    double d = i;
    vectorPushBack( vec1, d*d);                    /* vec1.push_back(i) */
  }
  printf( "vec1 cap. = %u\n", vectorCapacity( vec1)); /* vec1.capacity()*/
  printf( "vec1 size = %u\n", vectorSize( vec1));     /* vec1.size()    */
  printf( "vec1[42] = %f\n",vectorGet( vec1, 42));    /* vec1[42]       */
  
  /* vec2 = vectorCreate();   MEMORY LEAK!! */
  vectorCopy( vec2, vec1);                            /* vec2 = vec2    */

  vectorSet( vec2, 42, 99.99);                     /* vec2[42] = 99.99  */
  printf( "vec1[42] = %f\n",vectorGet(vec1, 42));  /* vec1[42]  1764.00 */
  printf( "vec2[42] = %f\n",vectorGet(vec2, 42));  /* vec2[42]    99.99 */

  vectorPopBack( vec2);                            /* vec2.pop_back()   */
  printf( "vec2 cap. = %u\n", vectorCapacity( vec2)); /*vec2.capacity() */
  printf( "vec2 size = %u\n", vectorSize( vec2));     /* vec2.size()    */
  
  /* vec3 = vectorCreate();   MEMORY LEAK!! */
  vectorMove( vec3, vec1);                         /* vec3 = move(vec1) */
  printf( "vec3 size = %u\n", vectorSize( vec3));  /* vec3.size()       */
  printf( "vec1 size = %u\n", vectorSize( vec1));  /* vec1.size()       */

  vectorDestroy( vec1);                            /* destructor        */
  vectorDestroy( vec2);                            /* destructor        */
  vectorDestroy( vec3);                            /* destructor        */
  return 0;
}

A fenti program lefutása az alábbi eredményt kell szolgáltassa:

1
2
3
4
5
6
7
8
9
10
11
12
$ gcc -ansi -pedantic -Wall -W vecmain.c vector.c
$ ./a.out
vec1 size = 0
vec1 cap. = 128
vec1 size = 100
vec1[42] = 1764.000000
vec1[42] = 1764.000000
vec2[42] = 99.990000
vec2 cap. = 100
vec2 size = 99
vec3 size = 100
vec1 size = 0

A vector.h fogja tartalmazni az interfészt. Lényegében egy header fálj, ami az interfész függvények és a típusra specifikus konstansok deklarációja.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#ifndef VECTOR_H
#define VECTOR_H

#define VEC_EOK      0
#define VEC_ENOMEM   1
#define VEC_EINDEX   2
#define VEC_EEMPTY   3


struct VECTOR_S;

typedef struct VECTOR_S *vector_t; 


extern int vectorErrno;


extern vector_t vectorCreate();
extern void     vectorDestroy( vector_t);

extern unsigned vectorSize( vector_t v);
extern unsigned vectorCapacity( vector_t v);

extern vector_t vectorCopy( vector_t target, vector_t source);
extern vector_t vectorMove( vector_t target, vector_t source);

extern void     vectorSet( vector_t v, unsigned idx, double d);
extern double   vectorGet( vector_t v, unsigned idx);

extern void     vectorPushBack( vector_t v, double d);
extern void     vectorPopBack( vector_t v);

#endif /* VECTOR_H */

Az implementációt a vector1.c forrásfájlban heykezzük el.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#include <stdlib.h>
#include "vector.h"

#define INIT_CAPACITY 4

struct VECTOR_S
{
  unsigned  capacity_;
  unsigned  size_;
  double   *ptr_;
};

int vectorErrno;

static int vectorGrowBuffer( vector_t v)
{
  unsigned i;
  unsigned newCapacity = v->capacity_ ? 2*v->capacity_ : INIT_CAPACITY;
 
  double *oldPtr = v->ptr_;
  double *newPtr = (double *) malloc(newCapacity*sizeof(double));

  if ( NULL == newPtr )
  {
     vectorErrno = VEC_ENOMEM;
     return 0;
  }
  for (i = 0; i < v->size_; ++i)
    newPtr[i] = oldPtr[i];

  v->capacity_ = newCapacity;
  v->ptr_ = newPtr;
  free(oldPtr);

  return 1;
}

vector_t vectorCreate()
{
  vector_t v = (vector_t) malloc(sizeof(*v));
  if ( NULL == v )
  {
    vectorErrno = VEC_ENOMEM;
    return NULL;
  }
  v->capacity_ = 0;
  v->size_     = 0;
  v->ptr_      = NULL;

  vectorErrno = VEC_EOK;
  return v;
}

void vectorDestroy( vector_t v)
{
  free(v->ptr_);
  free(v);

  vectorErrno = VEC_EOK;
  return;
}

unsigned vectorCapacity( vector_t v)
{
  vectorErrno = VEC_EOK;
  return v->capacity_;
}

unsigned vectorSize( vector_t v)
{
  vectorErrno = VEC_EOK;
  return v->size_;
}

vector_t vectorCopy( vector_t target, vector_t source)
{
  if ( target != source )
  {
    free(target->ptr_);

    target->capacity_ = source->size_;  /* possibly shrinking */
    target->size_     = source->size_;  

    if ( source->size_ > 0 )
    {
      unsigned i;
      target->ptr_ = 
            (double *) malloc(target->capacity_*sizeof(double));
      if ( NULL == target->ptr_ )
      {
        vectorErrno = VEC_ENOMEM;
        return NULL;
      }
      for (i = 0; i < target->size_; ++i)
        target->ptr_[i] = source->ptr_[i];
    }
    else
    {
      target->ptr_ = NULL;
    }
  }
  vectorErrno = VEC_EOK;
  return target;
}

extern vector_t vectorMove( vector_t target, vector_t source)
{
  if ( target != source )
  {
    free(target->ptr_);

    target->capacity_ = source->capacity_;  
    target->size_     = source->size_;  
    target->ptr_      = source->ptr_;       /* move */
 
    source->capacity_ = 0;
    source->size_     = 0;
    source->ptr_      = NULL;
  }
  vectorErrno = VEC_EOK;
  return target;
}

void vectorSet( vector_t v, unsigned idx, double d)
{
  if ( idx >= v->size_ )
  {
    vectorErrno = VEC_EINDEX;
    return;
  }
  v->ptr_[idx] = d;

  vectorErrno = VEC_EOK;
  return;
}

double vectorGet( vector_t v, unsigned idx)
{
  if ( idx >= v->size_ )
  {
    vectorErrno = VEC_EINDEX;
    return 0.0;
  }
  vectorErrno = VEC_EOK;
  return v->ptr_[idx];
}

void vectorPushBack( vector_t v, double d)
{
  if ( v->size_ == v->capacity_ )
  {
    if ( ! vectorGrowBuffer(v) )
      return;
  }
  v->ptr_[v->size_++] = d;

  vectorErrno = VEC_EOK;
  return; 
}

void vectorPopBack( vector_t v)
{
  if ( v->size_ == 0 )
  {
    vectorErrno = VEC_EEMPTY;
    return; 
  }
  --v->size_;

  vectorErrno = VEC_EOK;
  return; 
}
 

A fenti megoldás a double vektorok egy megfelelő megvalósítása. Mi lesz azonban a többi típussal. Hogyan tudjuk álalánosítani a megoldásunkat?

  1. Példányosítás: Újabb típusok generálása. Így működnek a C++ template-ek, de erre nincsen jó mehanizmus C-ben. Szimulálhatjuk preprocesszor varázslással.

  2. Típustörlés: Minden típust ugyanaz a kódpéldány szolgál ki. Így működik a Java Generic. Ezt közelítjük az alábbi C megoldással.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <string.h>
#include "vector.h"

void copyDouble(void *tgt, void *src)
{
  memcpy(tgt,src,sizeof(double));
}	

int main()
{
  int i;
  vector_t vec1 = vectorCreate(sizeof(double),copyDouble); /* vector_t vec1; */
  vector_t vec2 = vectorCreate(sizeof(double),copyDouble); /* vector_t vec2; */
  vector_t vec3 = vectorCreate(sizeof(double),copyDouble); /* vector_t vec3; */

  printf( "vec1 cap. = %lu\n", vectorCapacity( vec1));   /* vec1.capacity()   */
  printf( "vec1 size = %lu\n", vectorSize(vec1));        /* vec1.size()    */

  for(i = 0; i < 100; ++i)
  {
    double d = i*i;  
    vectorPushBack( vec1, &d);                          /* vec1.push_back(i) */
  }
  printf( "vec1 cap. = %lu\n", vectorCapacity( vec1));  /* vec1.capacity()   */
  printf( "vec1 size = %lu\n", vectorSize( vec1));      /* vec1.size()       */
  printf( "vec1[42] = %f\n",*(double*)vectorAt(vec1,42)); /* vec1[42]        */
  
  vectorCopy( vec2, vec1);                              /* vec2 = vec2       */

  *(double*)vectorAt( vec2, 42) = 99.99;                 /* vec[2] = 99.99   */
  printf( "vec1[42] = %f\n",*(double*)vectorAt(vec1,42));/* vec1[42] 1764.00 */
  printf( "vec2[42] = %f\n",*(double*)vectorAt(vec2,42));/* vec2[42]   99.99 */

  vectorPopBack( vec2);                                 /* vec2.pop_back()   */
  printf( "vec2 cap. = %lu\n", vectorCapacity( vec2));  /* vec2.capacity()   */
  printf( "vec2 size = %lu\n", vectorSize( vec2));      /* vec2.size()       */
  
  vectorMove( vec3, vec1);                              /* vec3 = move(vec1) */
  printf( "vec3 size = %lu\n", vectorSize( vec3));      /* vec3.size()       */
  printf( "vec1 size = %lu\n", vectorSize( vec1));      /* vec1.size()       */

  vectorDestroy( vec1);                                 /* destructor        */
  vectorDestroy( vec2);                                 /* destructor        */
  vectorDestroy( vec3);                                 /* destructor        */
  return 0;
}

A módosított interfész:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#ifndef VECTOR_H
#define VECTOR_H

#include <stddef.h>

#define VEC_EOK      0
#define VEC_ENOMEM   1
#define VEC_EINDEX   2
#define VEC_EEMPTY   3


struct VECTOR_S;

typedef struct VECTOR_S *vector_t; 
typedef void (*elem_copy_t)(void *tgt, void *src);

extern int vectorErrno;

extern vector_t vectorCreate( size_t esize, elem_copy_t cpyfunc);
extern void     vectorDestroy( vector_t);

extern size_t   vectorSize( vector_t v);
extern size_t   vectorCapacity( vector_t v);

extern vector_t vectorCopy( vector_t target, vector_t source);
extern vector_t vectorMove( vector_t target, vector_t source);

extern void    *vectorAt( vector_t v, size_t idx);

extern void     vectorPushBack( vector_t v, void *src);
extern void     vectorPopBack( vector_t v);

#endif /* VECTOR_H */

A megvalósítás:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#include <stdio.h>

#include <stddef.h>
#include <stdlib.h>
#include "vector.h"

#define INIT_CAPACITY 4

struct VECTOR_S
{
  size_t      capacity_;
  size_t      size_;
  char       *ptr_;
  size_t      esize_;
  elem_copy_t cpyfun_;
};

int vectorErrno;

static int vectorGrowBuffer( vector_t v)
{
  size_t i;
  size_t newCapacity = v->capacity_ ? 2*v->capacity_ 
                                    : INIT_CAPACITY;
 
  char *oldPtr = v->ptr_;
  char *newPtr = malloc(newCapacity * v->esize_);

  if ( NULL == newPtr )
  {
     vectorErrno = VEC_ENOMEM;
     return 0;
  }
  for (i = 0; i < v->size_; ++i)
  {
    v->cpyfun_( newPtr+(i*v->esize_), oldPtr+(i*v->esize_));
  }

  v->capacity_ = newCapacity;
  v->ptr_ = newPtr;
  free(oldPtr);

  return 1;
}

vector_t vectorCreate( size_t esize, elem_copy_t cpyfun)
{
  vector_t v = (vector_t) malloc(sizeof(*v));
  if ( NULL == v )
  {
    vectorErrno = VEC_ENOMEM;
    return NULL;
  }
  v->capacity_ = 0;
  v->size_     = 0;
  v->ptr_      = NULL;
  v->esize_    = esize;
  v->cpyfun_   = cpyfun;

  vectorErrno = VEC_EOK;
  return v;
}

void vectorDestroy( vector_t v)
{
  free(v->ptr_);
  free(v);

  vectorErrno = VEC_EOK;
  return;
}

size_t vectorCapacity( vector_t v)
{
  vectorErrno = VEC_EOK;
  return v->capacity_;
}

size_t vectorSize( vector_t v)
{
  vectorErrno = VEC_EOK;
  return v->size_;
}

vector_t vectorCopy( vector_t target, vector_t source)
{
  if ( target != source )
  {
    free(target->ptr_);

    target->capacity_ = source->size_;  /* possibly shrinking */
    target->size_     = source->size_;  
    target->cpyfun_   = source->cpyfun_;

    if ( source->size_ > 0 )
    {
      size_t i;
      size_t esz = source->esize_;
      target->ptr_ = malloc(target->capacity_ * esz);
      if ( NULL == target->ptr_ )
      {
        vectorErrno = VEC_ENOMEM;
        return NULL;
      }
      for (i = 0; i < target->size_; ++i)
        source->cpyfun_( target->ptr_+i*esz, source->ptr_+i*esz);
    }
    else
    {
      target->ptr_ = NULL;
    }
  }
  vectorErrno = VEC_EOK;
  return target;
}

extern vector_t vectorMove( vector_t target, vector_t source)
{
  if ( target != source )
  {
    free(target->ptr_);

    target->capacity_ = source->capacity_;  
    target->size_     = source->size_;  
    target->ptr_      = source->ptr_;       /* move */
 
    source->capacity_ = 0;
    source->size_     = 0;
    source->ptr_      = NULL;
  }
  vectorErrno = VEC_EOK;
  return target;
}

void *vectorAt( vector_t v, size_t idx)
{
  if ( idx >= v->size_ )
  {
    vectorErrno = VEC_EINDEX;
    return NULL;
  }
  vectorErrno = VEC_EOK;
  return v->ptr_ + idx*v->esize_;
}

void vectorPushBack( vector_t v, void *srcptr)
{
  void *tgtptr;

  if ( v->size_ == v->capacity_ )
  {
    if ( ! vectorGrowBuffer(v) )
      return;
  }
  tgtptr = v->ptr_ + v->esize_*v->size_;
  v->cpyfun_( tgtptr, srcptr);
  ++v->size_;
  vectorErrno = VEC_EOK;
  return; 
}

void vectorPopBack( vector_t v)
{
  if ( v->size_ == 0 )
  {
    vectorErrno = VEC_EEMPTY;
    return; 
  }
  --v->size_;

  vectorErrno = VEC_EOK;
  return; 
}

Fordítás, szerkesztés, futtatás:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ gcc -ansi -pedantic -Wall -W vecmain.c vector.c
$ ./a.out
vec1 cap. = 0
vec1 size = 0
vec1 cap. = 128
vec1 size = 100
vec1[42] = 1764.000000
vec1[42] = 1764.000000
vec2[42] = 99.990000
vec2 cap. = 100
vec2 size = 99
vec3 size = 100
vec1 size = 0

Ellenőrizzük a megoldásunk helyességét a memória-elszivárgás szempontjából:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
==492== Memcheck, a memory error detector
==492== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==492== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==492== Command: ./a.out
==492==
vec1 cap. = 0
vec1 size = 0
vec1 cap. = 128
vec1 size = 100
vec1[42] = 1764.000000
vec1[42] = 1764.000000
vec2[42] = 99.990000
vec2 cap. = 100
vec2 size = 99
vec3 size = 100
vec1 size = 0
==492==
==492== HEAP SUMMARY:
==492==     in use at exit: 0 bytes in 0 blocks
==492==   total heap usage: 11 allocs, 11 frees, 3,448 bytes allocated
==492==
==492== All heap blocks were freed -- no leaks are possible
==492==
==492== For counts of detected and suppressed errors, rerun with: -v
==492== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)