Subject: Re: Structured Programming (was Re: ?The end of software secrecy is almost upon us?)
Anyway, to get the discussion back on track:
I put together an example here <
https://gitlab.com/ldo/a_structured_discipline_of_programming>. This
is an extension module that offers services to Python code, because
that is just about the worst example I could think of, for those who
think gotos can cut it.
Problems I am trying to address:
* Leaking memory: allocating something and forgetting to dispose of
it
* Double-free: the opposite problem, trying to free a memory block
that has already been freed
The basic structured-programming pattern I follow is:
1: Initialize a pointer variable to `NULL`
2: Allocate memory for the pointer (this might fail)
3: Correctly free the pointer memory, regardless of allocation
success/failure
This project uses the implementation of a Python extension module as a
specific example, since Python itself is a highly dynamic language,
and so code that is written in a lower-level-language like C for
interfacing to Python typically has to deal a lot with allocation and management of Python objects and other dynamic memory.
In more concrete terms, the code pattern looks like this:
PyObject * obj = NULL; /* step 1 */
do /*once*/
{
... possible other stuff ...
allocate obj; /* step 2 */
if (PyErr_Occurred())
break;
... possible other stuff using obj ...
}
while (false);
Py_XDECREF(obj); /* step 3 */
Assuming there are no `goto`s in the ?...? parts (or `return`
statements, or shenanigans like `setjmp`(3)/`longjmp`(3)), it should
be clear that, regardless of what happens in those parts (provided
they don?t free or reallocate `obj`), any memory allocated to `obj`
will be correctly freed.
Key to this pattern is the fact that `Py_XDECREF` is a noop (and
raises no error) if it is passed a `NULL` pointer. This is also the
semantics of the `free`(3) routine in the C standard library, and I
recommend that any memory-disposal routine you create implement the
same behaviour. It saves checking for unallocated storage in the
cleanup code: just unconditionally dispose of everything temporary
that might have been allocated.
The above basic structure scales easily. For example, coping with
multiple objects needing allocating:
PyObject * obj1 = NULL;
PyObject * obj2 = NULL;
do /*once*/
{
... possible other stuff ...
allocate obj1;
if (PyErr_Occurred())
break;
... possible other stuff using obj1 ...
allocate obj2;
if (PyErr_Occurred())
break;
... possible other stuff using obj1 and obj2 ...
}
while (false);
Py_XDECREF(obj1);
Py_XDECREF(obj2);
Nested blocks:
PyObject * obj1 = NULL;
do /*once*/
{
... possible other stuff ...
allocate obj1;
if (PyErr_Occurred())
break;
... possible other stuff using obj1 ...
{
PyObject * obj2 = NULL;
do /*once*/
{
... even more possible stuff using obj1 ...
allocate obj2;
if (PyErr_Occurred())
break;
... possible other stuff using obj1 and obj2 ...
}
while (false);
Py_XDECREF(obj2);
}
if (PyErr_Occurred())
break;
... yet more possible stuff using obj1 ...
}
while (false);
Py_XDECREF(obj1);
Allocation within loops:
for (ssize_t i = 0;;)
{
if (i == nr_items)
break;
PyObject * const item = PyTuple_GetItem(items, i);
if (item == NULL)
break;
{
PyObject * key = NULL;
PyObject * value = NULL;
do /*once*/
{
... construct key ...
if (PyErr_Occurred())
break;
... construct value ...
if (PyErr_Occurred())
break;
if (PyDict_SetItem(tempresult, key, value) < 0)
break;
}
while (false);
Py_XDECREF(key);
Py_XDECREF(value);
}
if (PyErr_Occurred())
break;
++i;
} /*for*/
if (PyErr_Occurred())
break;
Etc, etc.
--- PyGate Linux v1.5.19
* Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)