diff --git a/element.go b/element.go
index 2e1e085175f2d34b68bf057b458a8eafd1a18f51..ddc6d47c16ae66931aa0016f7df7f4524ae0147e 100644
--- a/element.go
+++ b/element.go
@@ -23,6 +23,12 @@ package pbc
 
 /*
 #include <pbc/pbc.h>
+
+struct element_s* newElementStruct() { return malloc(sizeof(struct element_s)); }
+void freeElementStruct(struct element_s* x) {
+	element_clear(x);
+	free(x);
+}
 */
 import "C"
 
@@ -108,12 +114,12 @@ type Element struct {
 }
 
 func clearElement(element *Element) {
-	C.element_clear(element.cptr)
+	C.freeElementStruct(element.cptr)
 }
 
 func makeUncheckedElement(pairing *Pairing, initialize bool, field Field) *Element {
 	element := &Element{
-		cptr:    &C.struct_element_s{},
+		cptr:    C.newElementStruct(),
 		pairing: pairing,
 	}
 	if initialize {
diff --git a/element_arith.go b/element_arith.go
index f9874a1756d1caf3d75827aa2e4faf6c9b1a20bf..81573f4f5f70c44f81ef1968e732a4980d223e01 100644
--- a/element_arith.go
+++ b/element_arith.go
@@ -23,6 +23,18 @@ package pbc
 
 /*
 #include <pbc/pbc.h>
+
+struct element_pp_s* newElementPPStruct() { return malloc(sizeof(struct element_pp_s)); }
+void freeElementPPStruct(struct element_pp_s* x) {
+	element_pp_clear(x);
+	free(x);
+}
+
+struct pairing_pp_s* newPairingPPStruct() { return malloc(sizeof(struct pairing_pp_s)); }
+void freePairingPPStruct(struct pairing_pp_s* x) {
+	pairing_pp_clear(x);
+	free(x);
+}
 */
 import "C"
 
@@ -141,7 +153,7 @@ func (el *Element) MulBig(x *Element, i *big.Int) *Element {
 	if el.checked {
 		el.checkCompatible(x)
 	}
-	C.element_mul_mpz(el.cptr, x.cptr, &big2mpz(i)[0])
+	C.element_mul_mpz(el.cptr, x.cptr, &big2mpz(i).i[0])
 	return el
 }
 
@@ -257,7 +269,7 @@ func (el *Element) PowBig(x *Element, i *big.Int) *Element {
 	if el.checked {
 		el.checkCompatible(x)
 	}
-	C.element_pow_mpz(el.cptr, x.cptr, &big2mpz(i)[0])
+	C.element_pow_mpz(el.cptr, x.cptr, &big2mpz(i).i[0])
 	return el
 }
 
@@ -286,7 +298,7 @@ func (el *Element) Pow2Big(x *Element, i *big.Int, y *Element, j *big.Int) *Elem
 	if el.checked {
 		el.checkAllCompatible(x, y)
 	}
-	C.element_pow2_mpz(el.cptr, x.cptr, &big2mpz(i)[0], y.cptr, &big2mpz(j)[0])
+	C.element_pow2_mpz(el.cptr, x.cptr, &big2mpz(i).i[0], y.cptr, &big2mpz(j).i[0])
 	return el
 }
 
@@ -316,7 +328,7 @@ func (el *Element) Pow3Big(x *Element, i *big.Int, y *Element, j *big.Int, z *El
 	if el.checked {
 		el.checkAllCompatible(x, y, z)
 	}
-	C.element_pow3_mpz(el.cptr, x.cptr, &big2mpz(i)[0], y.cptr, &big2mpz(j)[0], z.cptr, &big2mpz(k)[0])
+	C.element_pow3_mpz(el.cptr, x.cptr, &big2mpz(i).i[0], y.cptr, &big2mpz(j).i[0], z.cptr, &big2mpz(k).i[0])
 	return el
 }
 
@@ -370,7 +382,7 @@ func (power *Power) PowZn(target *Element, i *Element) *Element {
 }
 
 func clearPower(power *Power) {
-	C.element_pp_clear(power.pp)
+	C.freeElementPPStruct(power.pp)
 }
 
 // PreparePower generates pre-processing data for repeatedly exponentiating el.
@@ -379,7 +391,7 @@ func clearPower(power *Power) {
 func (el *Element) PreparePower() *Power {
 	power := &Power{
 		source: el,
-		pp:     &C.struct_element_pp_s{},
+		pp:     C.newElementPPStruct(),
 	}
 	C.element_pp_init(power.pp, el.cptr)
 	runtime.SetFinalizer(power, clearPower)
@@ -395,7 +407,7 @@ func (el *Element) PowerBig(power *Power, i *big.Int) *Element {
 	if el.checked {
 		el.checkCompatible(power.source)
 	}
-	C.element_pp_pow(el.cptr, &big2mpz(i)[0], power.pp)
+	C.element_pp_pow(el.cptr, &big2mpz(i).i[0], power.pp)
 	return el
 }
 
@@ -551,7 +563,7 @@ func (pairer *Pairer) Pair(target *Element, y *Element) *Element {
 }
 
 func clearPairer(pairer *Pairer) {
-	C.pairing_pp_clear(pairer.pp)
+	C.freePairingPPStruct(pairer.pp)
 }
 
 // PreparePairer generates pre-processing data for repeatedly pairing el. The
@@ -566,7 +578,7 @@ func (el *Element) PreparePairer() *Pairer {
 	}
 	pairer := &Pairer{
 		source: el,
-		pp:     &C.struct_pairing_pp_s{},
+		pp:     C.newPairingPPStruct(),
 	}
 	C.pairing_pp_init(pairer.pp, el.cptr, el.pairing.cptr)
 	runtime.SetFinalizer(pairer, clearPairer)
diff --git a/element_io.go b/element_io.go
index 2a80dd449815d2847aa632ff8e42ac576a868952..7007556877cea95df0b00058c90f714c4a3f2cbe 100644
--- a/element_io.go
+++ b/element_io.go
@@ -44,9 +44,9 @@ func (el *Element) BigInt() *big.Int {
 	if el.checked {
 		el.checkInteger()
 	}
-	mpz := newMpz()
-	C.element_to_mpz(&mpz[0], el.cptr)
-	return mpz2big(mpz)
+	m := newMpz()
+	C.element_to_mpz(&m.i[0], el.cptr)
+	return mpz2big(m)
 }
 
 // Set sets the value of el to be the same as src.
@@ -83,7 +83,7 @@ func (el *Element) SetBig(i *big.Int) *Element {
 	if el.checked {
 		el.checkInteger()
 	}
-	C.element_set_mpz(el.cptr, &big2mpz(i)[0])
+	C.element_set_mpz(el.cptr, &big2mpz(i).i[0])
 	return el
 }
 
diff --git a/generation.go b/generation.go
index bb9ce576a0760f765b189c6668f130c1340e2d70..a69cf16137c7195d8e292a4c26079dd7d354c943 100644
--- a/generation.go
+++ b/generation.go
@@ -82,7 +82,7 @@ func GenerateA(rbits uint32, qbits uint32) *Params {
 // More details: https://crypto.stanford.edu/pbc/manual/ch08s03.html
 func GenerateA1(r *big.Int) *Params {
 	params := makeParams()
-	C.pbc_param_init_a1_gen(params.cptr, &big2mpz(r)[0])
+	C.pbc_param_init_a1_gen(params.cptr, &big2mpz(r).i[0])
 	return params
 }
 
diff --git a/gmp_big.go b/gmp_big.go
index e315ae2345b7db0ec8adbf5de35bcd63db97d174..555fc5d7bed1ebfcbd83f7fe73931f8cc9d3954e 100644
--- a/gmp_big.go
+++ b/gmp_big.go
@@ -32,46 +32,50 @@ import (
 	"unsafe"
 )
 
+type mpz struct {
+	i C.mpz_t
+}
+
 var wordSize C.size_t
 var bitsPerWord C.size_t
 
-func clearMpz(x *C.mpz_t) {
-	C.mpz_clear(&x[0])
+func clearMpz(x *mpz) {
+	C.mpz_clear(&x.i[0])
 }
 
-func newMpz() *C.mpz_t {
-	out := &C.mpz_t{}
-	C.mpz_init(&out[0])
+func newMpz() *mpz {
+	out := &mpz{}
+	C.mpz_init(&out.i[0])
 	runtime.SetFinalizer(out, clearMpz)
 	return out
 }
 
 // big2thisMpz imports the value of num into out
-func big2thisMpz(num *big.Int, out *C.mpz_t) {
+func big2thisMpz(num *big.Int, out *mpz) {
 	words := num.Bits()
 	if len(words) > 0 {
-		C.mpz_import(&out[0], C.size_t(len(words)), -1, wordSize, 0, 0, unsafe.Pointer(&words[0]))
+		C.mpz_import(&out.i[0], C.size_t(len(words)), -1, wordSize, 0, 0, unsafe.Pointer(&words[0]))
 	}
 }
 
 // big2mpz allocates a new mpz_t and imports a big.Int value
-func big2mpz(num *big.Int) *C.mpz_t {
+func big2mpz(num *big.Int) *mpz {
 	out := newMpz()
 	big2thisMpz(num, out)
 	return out
 }
 
 // mpz2thisBig imports the value of num into out
-func mpz2thisBig(num *C.mpz_t, out *big.Int) {
-	wordsNeeded := (C.mpz_sizeinbase(&num[0], 2) + (bitsPerWord - 1)) / bitsPerWord
+func mpz2thisBig(num *mpz, out *big.Int) {
+	wordsNeeded := (C.mpz_sizeinbase(&num.i[0], 2) + (bitsPerWord - 1)) / bitsPerWord
 	words := make([]big.Word, wordsNeeded)
 	var wordsWritten C.size_t
-	C.mpz_export(unsafe.Pointer(&words[0]), &wordsWritten, -1, wordSize, 0, 0, &num[0])
+	C.mpz_export(unsafe.Pointer(&words[0]), &wordsWritten, -1, wordSize, 0, 0, &num.i[0])
 	out.SetBits(words)
 }
 
 // mpz2big allocates a new big.Int and imports an mpz_t value
-func mpz2big(num *C.mpz_t) (out *big.Int) {
+func mpz2big(num *mpz) (out *big.Int) {
 	out = &big.Int{}
 	mpz2thisBig(num, out)
 	return
diff --git a/pairing.go b/pairing.go
index 69ac21380bcd7010b5fa27655804bcdb292efdd7..6abc8974e9cd2a0f2cea5370f5f01f800b42f5b3 100644
--- a/pairing.go
+++ b/pairing.go
@@ -23,6 +23,12 @@ package pbc
 
 /*
 #include <pbc/pbc.h>
+
+struct pairing_s* newPairingStruct() { return malloc(sizeof(struct pairing_s)); }
+void freePairingStruct(struct pairing_s* x) {
+	pairing_clear(x);
+	free(x);
+}
 */
 import "C"
 
@@ -152,11 +158,11 @@ func (pairing *Pairing) NewUncheckedElement(field Field) *Element {
 }
 
 func clearPairing(pairing *Pairing) {
-	C.pairing_clear(pairing.cptr)
+	C.freePairingStruct(pairing.cptr)
 }
 
 func makePairing() *Pairing {
-	pairing := &Pairing{cptr: &C.struct_pairing_s{}}
+	pairing := &Pairing{cptr: C.newPairingStruct()}
 	runtime.SetFinalizer(pairing, clearPairing)
 	return pairing
 }
diff --git a/params.go b/params.go
index 314d31cc49569ef4bd890fc19692865f550c9a2f..0e20843559da389d02cc257365015808d7ad993b 100644
--- a/params.go
+++ b/params.go
@@ -25,6 +25,12 @@ package pbc
 #include <pbc/pbc.h>
 #include "memstream.h"
 
+struct pbc_param_s* newParamStruct() { return malloc(sizeof(struct pbc_param_s)); }
+void freeParamStruct(struct pbc_param_s* x) {
+	pbc_param_clear(x);
+	free(x);
+}
+
 int param_out_str_wrapper(char** bufp, size_t* sizep, pbc_param_t p) {
 	memstream_t* stream = pbc_open_memstream();
 	if (stream == NULL) return 0;
@@ -102,11 +108,11 @@ func (params *Params) String() string {
 }
 
 func clearParams(params *Params) {
-	C.pbc_param_clear(params.cptr)
+	C.freeParamStruct(params.cptr)
 }
 
 func makeParams() *Params {
-	params := &Params{cptr: &C.struct_pbc_param_s{}}
+	params := &Params{cptr: C.newParamStruct()}
 	runtime.SetFinalizer(params, clearParams)
 	return params
 }
diff --git a/pbc_test.go b/pbc_test.go
index af99836580ee36faaab8c55ecc635a1f6b4477ea..8f2fe3addb8f9f0fa8713895aa7c75b434105cd6 100644
--- a/pbc_test.go
+++ b/pbc_test.go
@@ -24,6 +24,7 @@ package pbc
 import (
 	"crypto/sha256"
 	"math/big"
+	"runtime"
 	"testing"
 )
 
@@ -523,3 +524,11 @@ func TestZhangKim(t *testing.T) {
 		t.Fatal("signature does not verify")
 	}
 }
+
+// TestGC ensures that there are no errors when running struct finalizers.
+func TestGC(t *testing.T) {
+	TestBLS(t)
+	for i := 0; i < 5; i++ { // Multiple rounds to resolve dependencies
+		runtime.GC()
+	}
+}
diff --git a/utils.go b/utils.go
index 6c62d0292aa7a365185a49eac78b430cc3b26bbf..d41b36575c788dfdcbcdef5ab41e79745cdc2487 100644
--- a/utils.go
+++ b/utils.go
@@ -81,10 +81,10 @@ func SetRandomProvider(provider RandomSource) {
 
 //export goGenerateRandom
 func goGenerateRandom(out, limit unsafe.Pointer) {
-	outPtr := (*C.mpz_t)(out)
-	limitPtr := (*C.mpz_t)(limit)
-	r := randomProvider.Rand(mpz2big(limitPtr))
-	big2thisMpz(r, outPtr)
+	outMpz := &mpz{i: *(*C.mpz_t)(out) }
+	limitMpz := &mpz{i: *(*C.mpz_t)(limit) }
+	r := randomProvider.Rand(mpz2big(limitMpz))
+	big2thisMpz(r, outMpz)
 }
 
 type readerProvider struct {