Page 145, The C programming Language 2 Edition
/* install: put (name, defn) in hashtab */
struct nlist *install(char *name, char *defn)
{
struct nlist *np;
unsigned hashval;
if ((np = lookup(name)) == NULL) { /* not found */
np = (struct nlist *) malloc(sizeof(*np));
if (np == NULL || (np->name = strdup(name)) == NULL)
return NULL;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
} else /* already there */
free((void *) np->defn); /* free previous defn */
if ((np->defn = strdup(defn)) == NULL)
return NULL;
return np;
}
On Fri, 21 Jun 2024 09:45:21 -0300, Thiago Adams wrote:
Page 145, The C programming Language 2 Edition
/* install: put (name, defn) in hashtab */
struct nlist *install(char *name, char *defn)
{
struct nlist *np;
unsigned hashval;
if ((np = lookup(name)) == NULL) { /* not found */
np = (struct nlist *) malloc(sizeof(*np));
if (np == NULL || (np->name = strdup(name)) == NULL)
return NULL;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
} else /* already there */
free((void *) np->defn); /* free previous defn */
if ((np->defn = strdup(defn)) == NULL)
return NULL;
return np;
}
struct nlist *install(char *name, char *defn)
{
struct nlist *np = NULL;
struct nlist *result = NULL;
unsigned hashval;
do /*once*/
{
result = lookup(name);
if (result != NULL)
break;
np = (struct nlist *)calloc(1, sizeof struct nlist);
if (np == NULL)
break;
np->defn = strdup(defn);
if (np->defn == NULL)
break;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
result = np;
np = NULL; /* so I don?t dispose of it yet */
}
while (false);
if (np != NULL)
{
free(np->defn);
} /*if*/
free(np);
return
result;
} /*install*/
Why are you so afraid of `goto' ...
I think you forget to set np->name (and to free() it in
case of error).
struct nlist *install(char *name, char *defn)
{
struct nlist *np = NULL;
struct nlist *result = NULL;
unsigned hashval;
do /*once*/
{
result = lookup(name);
if (result != NULL)
break;
np = (struct nlist *)calloc(1, sizeof struct nlist);
if (np == NULL)
break;
np->defn = strdup(defn);
if (np->defn == NULL)
break;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
result = np;
np = NULL; /* so I don't dispose of it yet */
}
while (false);
if (np != NULL)
{
free(np->defn);
} /*if*/
free(np);
return
result;
} /*install*/
name (and to free() it in case of error). You setcode did it for an existing node as well. My absolutely
defn only in case of a new node, whereas the original
On Sun, 23 Jun 2024 02:23:43 +0300, Anton Shepelev wrote:
Why are you so afraid of `goto' ...
Look up the concept of a “Nassi-Shneiderman diagram”. It allows arbitrary nesting of complex code, with dynamic allocation going on, while
minimizing flow-control headaches. The example I posted was a simple one;
I have more complex ones if you want to see.
On Fri, 21 Jun 2024 09:45:21 -0300, Thiago Adams wrote:
Page 145, The C programming Language 2 Edition
/* install: put (name, defn) in hashtab */
struct nlist *install(char *name, char *defn)
{
struct nlist *np;
unsigned hashval;
if ((np = lookup(name)) == NULL) { /* not found */
np = (struct nlist *) malloc(sizeof(*np));
if (np == NULL || (np->name = strdup(name)) == NULL)
return NULL;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
} else /* already there */
free((void *) np->defn); /* free previous defn */
if ((np->defn = strdup(defn)) == NULL)
return NULL;
return np;
}
struct nlist *install(char *name, char *defn)
{
struct nlist *np = NULL;
struct nlist *result = NULL;
unsigned hashval;
do /*once*/
{
result = lookup(name);
if (result != NULL)
break;
np = (struct nlist *)calloc(1, sizeof struct nlist);
if (np == NULL)
break;
np->defn = strdup(defn);
if (np->defn == NULL)
break;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
result = np;
np = NULL; /* so I don’t dispose of it yet */
}
while (false);
if (np != NULL)
{
free(np->defn);
} /*if*/
free(np);
return
result;
} /*install*/
Lawrence D'Oliveiro:
struct nlist *install(char *name, char *defn)
{
struct nlist *np = NULL;
struct nlist *result = NULL;
unsigned hashval;
do /*once*/
{
result = lookup(name);
if (result != NULL)
break;
np = (struct nlist *)calloc(1, sizeof struct nlist);
if (np == NULL)
break;
np->defn = strdup(defn);
if (np->defn == NULL)
break;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
result = np;
np = NULL; /* so I don't dispose of it yet */
}
while (false);
if (np != NULL)
{
free(np->defn);
} /*if*/
free(np);
return
result;
} /*install*/
Why are you so afraid of `goto' that you must emulate it
with `while' and `break'?
I think you forget to set
name (and to free() it in case of error). You setcode did it for an existing node as well. My absolutely
defn only in case of a new node, whereas the original
untested version is below:
/* install: put (name, defn) in hashtab */
struct nlist *install(char *name, char *defn)
{ struct nlist *np;
unsigned hashval;
int new_nm, new_nd;
void* old_fn;
new_nm = 0; new_nd = 0; old_fn = NULL;
if ((np = lookup(name)) != NULL) /* short branch first */
{ old_fn = (void *)np->defn; /* do not free it here to */
goto set_defn; } /* avoid a side effect */
np = (struct nlist *) malloc(sizeof(*np));
if(np == NULL) goto error;
new_nd = 1;
if((np->name = strdup(name)) == NULL) goto error;
new_nm = 1;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
set_defn:
if ((np->defn = strdup(defn)) == NULL) goto error;
if( old_fn ) free( old_fn );
return np;
error:
if( new_nm ) free( np->name );
if( new_nd ) free( np );
return NULL;
}
On Fri, 21 Jun 2024 09:45:21 -0300, Thiago Adams wrote:
Page 145, The C programming Language 2 Edition
/* install: put (name, defn) in hashtab */
struct nlist *install(char *name, char *defn)
{
struct nlist *np;
unsigned hashval;
if ((np = lookup(name)) == NULL) { /* not found */
np = (struct nlist *) malloc(sizeof(*np));
if (np == NULL || (np->name = strdup(name)) == NULL)
return NULL;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
} else /* already there */
free((void *) np->defn); /* free previous defn */
if ((np->defn = strdup(defn)) == NULL)
return NULL;
return np;
}
Watch and learn:
struct nlist *install(char *name, char *defn)
{
struct nlist *existing = lookup(name);
if (existing) {
return existing;
} else {
struct nlist *np = calloc(1, sizeof (struct nlist));
char *dupname = strdup(name);
char *dupdefn = strdup(defn);
unsigned hashval = hash(name);
if (np && dupname && dupdefn) {
np->name = dupname;
np->defn = dupdefn;
np->next = hashtab[hashval];
hashtab[hashval] = np;
return np;
}
free(dupdefn);
free(dupname);
free(np);
return NULL;
}
}
I don't get why the goto crowd want to complicate it so
much.
Why are you so afraid of `goto' that you must emulate it
with `while' and `break'?
Why are you so fond of them that you add three extra state
variables that have to be (mentally and/or mathematically
tracked) just understand this supposedly simply function?
This, to me, is a textbook case of why goto is harmful. I
have spend considerable time jumping up and down checking
the state of all the variables and I am still not sure I
follow what this supposedly simple function is doing.
(And everyone seems keen on redundant casts. Why?)
What scatter-brained drivel. Watch and learn:
struct nlist *install(char *name, char *defn)
{
struct nlist *existing = lookup(name);
if (existing) {
return existing;
} else {
Will someone post a goto-less that fixes what I perceive as bugs in the original:
a. the failure to free() a newly allocated nlist in case
of a later error,
b. the failure to free() a newly allocated np->name in
case of a later error,
c. the failure to keep np->defn unchaged if the
allocation of the new defn value failed.
Why are you so afraid of `goto' ...
Look up the concept of a "Nassi-Shneiderman diagram". It
allows arbitrary nesting of complex code, with dynamic
allocation going on, while minimizing flow-control
headaches.
And while I'm at it, it makes sense to factor out the
table entry disposal code into a separate routine.
void np_free(struct nlist *np)
{
if (np != NULL)
{
free(np->name);
free(np->defn);
} /*if*/
free(np);
} /*np_free*/
Kaz Kylheku <643-408-1753@kylheku.com> writes:
On Fri, 21 Jun 2024 09:45:21 -0300, Thiago Adams wrote:
Page 145, The C programming Language 2 Edition
/* install: put (name, defn) in hashtab */
struct nlist *install(char *name, char *defn)
{
struct nlist *np;
unsigned hashval;
if ((np = lookup(name)) == NULL) { /* not found */
np = (struct nlist *) malloc(sizeof(*np));
if (np == NULL || (np->name = strdup(name)) == NULL)
return NULL;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
} else /* already there */
free((void *) np->defn); /* free previous defn */
if ((np->defn = strdup(defn)) == NULL)
return NULL;
return np;
}
[snip attempts at tidying up...]
Watch and learn:
struct nlist *install(char *name, char *defn)
{
struct nlist *existing = lookup(name);
if (existing) {
return existing;
} else {
struct nlist *np = calloc(1, sizeof (struct nlist));
char *dupname = strdup(name);
char *dupdefn = strdup(defn);
unsigned hashval = hash(name);
if (np && dupname && dupdefn) {
np->name = dupname;
np->defn = dupdefn;
np->next = hashtab[hashval];
hashtab[hashval] = np;
return np;
}
free(dupdefn);
free(dupname);
free(np);
return NULL;
}
}
You've over-simplified. The function needs to replace the definition
with a strdup'd string (introduction another way to fail) when the name
is found by lookup.
Kaz Kylheku:
What scatter-brained drivel. Watch and learn:
struct nlist *install(char *name, char *defn)
{
struct nlist *existing = lookup(name);
if (existing) {
return existing;
} else {
When the if-branch ends with a return, the else-branch is
redundant, and its body shall be written bare, removing one
(useless) level of nesting and indentation.
the failure to keep np->defn unchaged if the allocation
of the new defn value failed.
Not sure what the point is here.
Ben Bacarisse:
I don't get why the goto crowd want to complicate it so
much.
Will someone post a goto-less that fixes what I perceive as
bugs in the original:
a. the failure to free() a newly allocated nlist in case
of a later error,
b. the failure to free() a newly allocated np->name in
case of a later error,
c. the failure to keep np->defn unchaged if the
allocation of the new defn value failed.
And my perception be wrong, let us first establish the
actual bug(s).
Ben Bacarisse to Anton Shelelev:
Why are you so afraid of `goto' that you must emulate it
with `while' and `break'?
Why are you so fond of them that you add three extra state
variables that have to be (mentally and/or mathematically
tracked) just understand this supposedly simply function?
Because my attempts to fix the function without the extra
variables proved even worse.
This, to me, is a textbook case of why goto is harmful. I
have spend considerable time jumping up and down checking
the state of all the variables and I am still not sure I
follow what this supposedly simple function is doing.
I have tried to keep the structure simple: all the goto's
jump down, both the labels define isolated blocks, guared by
returns, to prevent fall-though.
On 23/06/2024 00:30, Lawrence D'Oliveiro wrote:
On Sun, 23 Jun 2024 02:23:43 +0300, Anton Shepelev wrote:
Why are you so afraid of `goto' ...
Look up the concept of a “Nassi-Shneiderman diagram”. It allows
arbitrary nesting of complex code, with dynamic allocation going on,
while minimizing flow-control headaches.
So far (with the exception of an example of tightly bound co-routines
being simulated in a single C function) I have not yet seen one that
can't [be done without goto].
I thought the challenge was to fix it as a single function.
struct nlist *install(const char *name, const char *defn)
{
struct nlist *node = lookup(name);
if (node) {
char *new_defn = strdup(defn);
if (new_defn) {
free(node->defn);
node->defn = new_defn;
return node;
}
else return NULL;
}
else {
struct nlist *new_node = malloc(sizeof *new_node);
char *new_name = strdup(name), *new_defn = strdup(defn);
if (new_node && new_name && new_defn) {
unsigned hashval = hash(name);
new_node->name = new_name;
new_node->defn = new_defn;
new_node->next = hashtab[hashval];
return hashtab[hashval] = new_node;
}
else {
free(new_defn);
free(new_name);
free(new_node);
return NULL;
}
}
}
We have four cases:
node with the name found
new definition allocated new definition not allocated
node with the name not found
new node, name and definition all allocated not all of new node,
name and definition allocated.
We can very simply reason about all of these situations.
On 2024-06-23, Anton Shepelev <anton.txt@gmail.moc> wrote:
Kaz Kylheku:
What scatter-brained drivel. Watch and learn:
struct nlist *install(char *name, char *defn)
{
struct nlist *existing = lookup(name);
if (existing) {
return existing;
} else {
When the if-branch ends with a return, the else-branch is
redundant, and its body shall be written bare, removing one
(useless) level of nesting and indentation.
I did that because there are declarations in the else case.
I avoid mising declarations and statements, because I consider
that a language misfeature.
In a block structured language, declarations should come first,
then statements.
... it is a (more or less) deeply nested imperative code
construct of loops where we want to leave from the innermost loop. Propagating the exit condition through all the nested loops "to the
surface" complicates the code here.
[...]
Trying to make gotos less bad than they can be is not usually an overall positive. Whenever I see a "good" use of goto, I try to see if there's
a better way with no use of goto.
So far (with the exception of an
example of tightly bound co-routines being simulated in a single C
function) I have not yet seen one that can't. There are sometimes
run-time considerations (giant state machines, for example) but even
there the structured code is probably clearer. The use of gotos in
those cases is not to improve the logic of the code but to placate the
code generator.
On 2024-06-23, Anton Shepelev <anton.txt@gmail.moc> wrote:
Kaz Kylheku:
What scatter-brained drivel. Watch and learn:
struct nlist *install(char *name, char *defn)
{
struct nlist *existing = lookup(name);
if (existing) {
return existing;
} else {
When the if-branch ends with a return, the else-branch is
redundant, and its body shall be written bare, removing one
(useless) level of nesting and indentation.
I did that because there are declarations in the else case.
I avoid mising declarations and statements, because I consider
that a language misfeature.
In a block structured language, declarations should come first,
then statements.
Trying to make gotos less bad than they can be is not usually an
overall positive. Whenever I see a "good" use of goto, I try to
see if there's a better way with no use of goto. So far [noted an
exception] I have not yet seen one that can't.
On Mon, 24 Jun 2024 00:25:56 +0100, Ben Bacarisse wrote:
struct nlist *install(const char *name, const char *defn)
{
struct nlist *node = lookup(name);
if (node) {
char *new_defn = strdup(defn);
if (new_defn) {
free(node->defn);
node->defn = new_defn;
return node;
}
else return NULL;
}
else {
struct nlist *new_node = malloc(sizeof *new_node);
char *new_name = strdup(name), *new_defn = strdup(defn);
if (new_node && new_name && new_defn) {
unsigned hashval = hash(name);
new_node->name = new_name;
new_node->defn = new_defn;
new_node->next = hashtab[hashval];
return hashtab[hashval] = new_node;
}
else {
free(new_defn);
free(new_name);
free(new_node);
return NULL;
}
}
}
We have four cases:
node with the name found
new definition allocated new definition not allocated
node with the name not found
new node, name and definition all allocated not all of new node,
name and definition allocated.
We can very simply reason about all of these situations.
Too many different paths in the control flow, though. I think it’s a good idea to minimize that.
On 24.06.2024 01:04, Kaz Kylheku wrote:
On 2024-06-23, Anton Shepelev <anton.txt@gmail.moc> wrote:
Kaz Kylheku:
What scatter-brained drivel. Watch and learn:
struct nlist *install(char *name, char *defn)
{
struct nlist *existing = lookup(name);
if (existing) {
return existing;
} else {
When the if-branch ends with a return, the else-branch is
redundant, and its body shall be written bare, removing one
(useless) level of nesting and indentation.
I was tempted to post the same comment before I noticed that:
I did that because there are declarations in the else case.
So I at least understand where Kaz was coming from and
abstained from a comment; I think it's a valid view.
I avoid mising declarations and statements, because I consider
that a language misfeature.
In a block structured language, declarations should come first,
then statements.
This is an age old concept, some programming languages even
require that (e.g. Simula, Pascal, just to name a few). But
starting with C++ I preferred the initialized declarations;
introduce objects where you use them, so that you have sort
of a "local scope"[*].
Anton Shepelev <anton.txt@gmail.moc> writes:
Ben Bacarisse:
I don't get why the goto crowd want to complicate it so
much.
Will someone post a goto-less that fixes what I perceive as
bugs in the original:
a. the failure to free() a newly allocated nlist in case
of a later error,
b. the failure to free() a newly allocated np->name in
case of a later error,
c. the failure to keep np->defn unchaged if the
allocation of the new defn value failed.
And my perception be wrong, let us first establish the
actual bug(s).
With the usual trepidation that this is untested (though I did compile
it), I'd write something like:
struct nlist *install(const char *name, const char *defn)
{
struct nlist *node = lookup(name);
if (node) {
char *new_defn = strdup(defn);
if (new_defn) {
free(node->defn);
node->defn = new_defn;
return node;
}
else return NULL;
}
else {
struct nlist *new_node = malloc(sizeof *new_node);
char *new_name = strdup(name), *new_defn = strdup(defn);
if (new_node && new_name && new_defn) {
unsigned hashval = hash(name);
new_node->name = new_name;
new_node->defn = new_defn;
new_node->next = hashtab[hashval];
return hashtab[hashval] = new_node;
}
else {
free(new_defn);
free(new_name);
free(new_node);
return NULL;
}
}
}
We have four cases:
node with the name found
new definition allocated
new definition not allocated
node with the name not found
new node, name and definition all allocated
not all of new node, name and definition allocated.
We can very simply reason about all of these situations. For example,
when is storage freed? Just before returning NULL because the name was
not in the table and one or more of the required allocations failed.
Are the calls to free legal? Yes, all are initialised with the return
from malloc-like functions and are never assigned. Pretty much anything
that you want to check can be checked by simple reasoning. In
particular, your (a), (b) and (c) can be checked quite easily.
(I've written out all the else clauses because I want to show the "fully structured" version. I admit that I might sometimes save three lines by putting "return NULL;" at the very end and allowing two cases to fall through.)
On Mon, 24 Jun 2024 00:25:56 +0100, Ben Bacarisse wrote:
struct nlist *install(const char *name, const char *defn)
{
struct nlist *node = lookup(name);
if (node) {
char *new_defn = strdup(defn);
if (new_defn) {
free(node->defn);
node->defn = new_defn;
return node;
}
else return NULL;
}
else {
struct nlist *new_node = malloc(sizeof *new_node);
char *new_name = strdup(name), *new_defn = strdup(defn);
if (new_node && new_name && new_defn) {
unsigned hashval = hash(name);
new_node->name = new_name;
new_node->defn = new_defn;
new_node->next = hashtab[hashval];
return hashtab[hashval] = new_node;
}
else {
free(new_defn);
free(new_name);
free(new_node);
return NULL;
}
}
}
We have four cases:
node with the name found
new definition allocated new definition not allocated
node with the name not found
new node, name and definition all allocated not all of new node,
name and definition allocated.
We can very simply reason about all of these situations.
Too many different paths in the control flow, though. I think it’s a good idea to minimize that.
Also:
You generally want parallel elements at the same level of
indentation.
The following structure can be grating on the eyes:
if (key == node->key)
return node;
else if (key < node->key)
return tree_search(node->right, key);
return tree_search(node->left, key);
This is just an example; I realize we are not handling the
case of the key being found.
This is better:
if (key == node->key)
return node;
else if (key < node->key)
return tree_search(node->right, key);
else
return tree_search(node->left, key);
The structure being recommended by Anton is most at home
in imperative situations like this:
stmt1;
stmt2;
if (condition1)
return early;
stmt3;
stmt4;
if (condition2)
return early;
stmt5;
...
When we have multiple cases that are all parallel and of
equivalent importance (they could be in in any order), not
so much.
Lawrence D'Oliveiro <ldo@nz.invalid> writes:
Too many different paths in the control flow, though. I think it’s a
good idea to minimize that.
Your non-solution has more.
It's much clearer (to me) to separate the cases and deal
with them individually.
On Mon, 24 Jun 2024 11:53:51 +0100, Ben Bacarisse wrote:
Lawrence D'Oliveiro <ldo@nz.invalid> writes:
Too many different paths in the control flow, though. I think it’s a
good idea to minimize that.
Your non-solution has more.
My solution only has one major flow of control: in the top and out the bottom. Everything else is error checks, and it is quite obvious where
they all go--through the same cleanup path.
On Mon, 24 Jun 2024 11:53:51 +0100, Ben Bacarisse wrote:
Lawrence D'Oliveiro <ldo@nz.invalid> writes:
Too many different paths in the control flow, though. I think it’s a
good idea to minimize that.
Your non-solution has more.
My solution only has one major flow of control: in the top and out the bottom.
Everything else is error checks, and it is quite obvious where
they all go--through the same cleanup path.
On Mon, 24 Jun 2024 11:39:49 +0200, David Brown wrote:
It's much clearer (to me) to separate the cases and deal
with them individually.
Except it becomes difficult to ensure that you have indeed tested all
those cases.
On Mon, 24 Jun 2024 11:39:49 +0200, David Brown wrote:
It's much clearer (to me) to separate the cases and deal
with them individually.
Except it becomes difficult to ensure that you have indeed tested all
those cases.
Anton Shepelev <anton.txt@gmail.moc> writes:
Ben Bacarisse:
I don't get why the goto crowd want to complicate it so
much.
Will someone post a goto-less that fixes what I perceive as
bugs in the original:
a. the failure to free() a newly allocated nlist in case
of a later error,
b. the failure to free() a newly allocated np->name in
case of a later error,
c. the failure to keep np->defn unchaged if the
allocation of the new defn value failed.
And my perception be wrong, let us first establish the
actual bug(s).
With the usual trepidation that this is untested (though I did compile
it), I'd write something like:
struct nlist *install(const char *name, const char *defn)
{
struct nlist *node = lookup(name);
if (node) {
char *new_defn = strdup(defn);
if (new_defn) {
free(node->defn);
node->defn = new_defn;
return node;
}
else return NULL;
}
else {
struct nlist *new_node = malloc(sizeof *new_node);
char *new_name = strdup(name), *new_defn = strdup(defn);
if (new_node && new_name && new_defn) {
unsigned hashval = hash(name);
new_node->name = new_name;
new_node->defn = new_defn;
new_node->next = hashtab[hashval];
return hashtab[hashval] = new_node;
}
else {
free(new_defn);
free(new_name);
free(new_node);
return NULL;
}
}
}
We have four cases:
node with the name found
new definition allocated
new definition not allocated
node with the name not found
new node, name and definition all allocated
not all of new node, name and definition allocated.
On 2024-06-24, Lawrence D'Oliveiro <ldo@nz.invalid> wrote:
On Mon, 24 Jun 2024 11:53:51 +0100, Ben Bacarisse wrote:
Lawrence D'Oliveiro <ldo@nz.invalid> writes:
Too many different paths in the control flow, though. I think it’s a >>>> good idea to minimize that.
Your non-solution has more.
My solution only has one major flow of control: in the top and out the
bottom.
This is false. Every branch in the code creates a separate control
flow path.
Everything else is error checks, and it is quite obvious where
they all go--through the same cleanup path.
Your solution intertwines both cases together and jumbles the error
handling between other logic, requiring the reader to untangle all
that.
Ben Bacarisse <ben@bsb.me.uk> writes:
Anton Shepelev <anton.txt@gmail.moc> writes:
Ben Bacarisse:
I don't get why the goto crowd want to complicate it so
much.
Will someone post a goto-less that fixes what I perceive as
bugs in the original:
a. the failure to free() a newly allocated nlist in case
of a later error,
b. the failure to free() a newly allocated np->name in
case of a later error,
c. the failure to keep np->defn unchaged if the
allocation of the new defn value failed.
And my perception be wrong, let us first establish the
actual bug(s).
With the usual trepidation that this is untested (though I did compile
it), I'd write something like:
struct nlist *install(const char *name, const char *defn)
{
struct nlist *node = lookup(name);
if (node) {
char *new_defn = strdup(defn);
if (new_defn) {
free(node->defn);
node->defn = new_defn;
return node;
}
else return NULL;
}
That's fine, certainly, but I'm a fan of early fail-and-bail, which
permits the didn't-fail side of the if to remain unindented:
if (node) {
char *new_defn = strdup(defn);
if (!new_defn)
return NULL;
free(node->defn);
node->defn = new_defn;
return node;
}
else {
struct nlist *new_node = malloc(sizeof *new_node);
char *new_name = strdup(name), *new_defn = strdup(defn);
if (new_node && new_name && new_defn) {
unsigned hashval = hash(name);
new_node->name = new_name;
new_node->defn = new_defn;
new_node->next = hashtab[hashval];
return hashtab[hashval] = new_node;
}
else {
free(new_defn);
free(new_name);
free(new_node);
return NULL;
}
}
I think I'd deviate a little more on this side, as I don't like doing
the strdups when you know the malloc has failed.
Again, early
fail-and-bail applies:
else {
struct nlist *new_node = malloc(sizeof *new_node);
if (!new_node)
return NULL;
char *new_name = strdup(name), *new_defn = strdup(defn);
if (!new_name || !new_defn) {
free(new_defn);
free(new_name);
free(new_node);
return NULL;
}
unsigned hashval = hash(name);
new_node->name = new_name;
new_node->defn = new_defn;
new_node->next = hashtab[hashval];
return hashtab[hashval] = new_node;
}
You could split the strdups too, of course, but it's itsy-bitsy.
In all
honesty, I'd probably actually code with gotos, and jump to whichever
bit of cleanup was relevant.
We have four cases:
node with the name found
new definition allocated
new definition not allocated
node with the name not found
new node, name and definition all allocated
not all of new node, name and definition allocated.
I once came across a quirky coding standard that would have split
this into 3 functions. The outermost if/else blocks would
have been two separate helper functions.
struct nlist *install(const char *name, const char *defn)
{
struct nlist *node = lookup(name);
if (node)
return update_node(node, defn);
else
return new_node(name, defn);
}
The principle being that every function should do one job, and be
visible in one small screenful. The choice of code structure in each of
the two helper functions is now simplified, as you don't need to
consider anything in the other helper function.
In all honesty, I'd probably actually code with gotos, and jump to
whichever bit of cleanup was relevant.
On 6/25/2024 6:06 AM, Ben Bacarisse wrote:
sometimes I think its an AI...
On 2024-06-26, Chris M. Thomasson <chris.m.thomasson.1@gmail.com> wrote:
sometimes I think its an AI...
It's an AI that curiously stopped responding to my posts early this year sometime, for no obvious REAson, other than losing arguments.
Indeed, it doesn't look ike a troll to me, though.
I think we can nickname it Miller, because it's Genuine Daft.
I think we can nickname it Miller, because it's Genuine Daft.
What is that "Miller" referring to?
On 2024-06-26, Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote:
I think we can nickname it Miller, because it's Genuine Daft.
What is that "Miller" referring to?
"Miller Genuine Draft" is a kind of horse urine marketed as beer.
On 2024-06-26, Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote:
I think we can nickname it Miller, because it's Genuine Daft.
What is that "Miller" referring to?
"Miller Genuine Draft" is a kind of horse urine marketed as beer.
On 26.06.2024 07:18, Kaz Kylheku wrote:
On 2024-06-26, Chris M. Thomasson <chris.m.thomasson.1@gmail.com> wrote:
sometimes I think its an AI...
This is indeed a familiar feeling when repeatedly reading stereotypical >contributions from certain frequently posting bots, erm.., posters.
(In this case I think, I fear, I hope it's a human, though.)
It's an AI that curiously stopped responding to my posts early this year
sometime, for no obvious REAson, other than losing arguments.
Indeed, it doesn't look ike a troll to me, though.
I think we can nickname it Miller, because it's Genuine Daft.
What is that "Miller" referring to?
That sounds a bit country-specific for an international newsgroup. And
we should probably avoid discussing beer - people can get very worked
up about that kind of thing. It's safer to stick to less controversial
and divisive topics, like politics or religion :-)
On 26/06/2024 12:55, Kaz Kylheku wrote:
On 2024-06-26, Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote: >>>> I think we can nickname it Miller, because it's Genuine Daft.
What is that "Miller" referring to?
"Miller Genuine Draft" is a kind of horse urine marketed as beer.
That sounds a bit country-specific for an international newsgroup. And
we should probably avoid discussing beer - people can get very worked up about that kind of thing. It's safer to stick to less controversial and divisive topics, like politics or religion :-)
On Wed, 26 Jun 2024 13:24:47 +0200, David Brown wrote:
That sounds a bit country-specific for an international newsgroup. And
we should probably avoid discussing beer - people can get very worked
up about that kind of thing. It's safer to stick to less controversial
and divisive topics, like politics or religion :-)
Beer seems to resemble religion in another way: each one claims their particular one is the only true one, all the others are false.
They can’t all be right on the first point, they can all be right on the second.
Page 145, The C programming Language 2 Edition
/* install: put (name, defn) in hashtab */
struct nlist *install(char *name, char *defn)
{
struct nlist *np;
unsigned hashval;
if ((np = lookup(name)) == NULL) { /* not found */
np = (struct nlist *) malloc(sizeof(*np));
if (np == NULL || (np->name = strdup(name)) == NULL)
return NULL;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
} else /* already there */
free((void *) np->defn); /* free previous defn */
if ((np->defn = strdup(defn)) == NULL)
return NULL;
return np;
}
result = lookup(name);
if (result != NULL)
{
char * const defn_temp = strdup(defn);
if (defn_temp == NULL)
break;
On Tue, 25 Jun 2024 11:47:50 +0300, Phil Carmody wrote:
In all honesty, I'd probably actually code with gotos, and jump to
whichever bit of cleanup was relevant.
Complicating the control flow, especially for cleanup paths which are
harder to test, is asking for trouble.
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 546 |
Nodes: | 16 (2 / 14) |
Uptime: | 148:49:12 |
Calls: | 10,383 |
Calls today: | 8 |
Files: | 14,054 |
D/L today: |
2 files (1,861K bytes) |
Messages: | 6,417,760 |