git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH] blame.c: prepare_lines should not call xrealloc for every line

From
David Kastrup <dak@gnu.org>
Date
Feb 5, 2014, 23:45 UTC
Message-ID
<87vbwtjf81.fsf@fencepost.gnu.org>
In-Reply-To
<xmqqob2l2ta7.fsf@gitster.dls.corp.google.com>
Junio C Hamano <gitster@pobox.com> writes:
Show 29 quoted lines
> David Kastrup <dak@gnu.org> writes:
>
>> Junio C Hamano <gitster@pobox.com> writes:
>>
>>> which I think is the prevalent style in our codebase.  The same for
>>> the other loop we see in the new code below.
>>>
>>>  - avoid assignments in conditionals when you do not have to.
>>
>> commit a77a48c259d9adbe7779ca69a3432e493116b3fd
>> Author: Junio C Hamano <gitster@pobox.com>
>> Date:   Tue Jan 28 13:55:59 2014 -0800
>>
>>     combine-diff: simplify intersect_paths() further
>> [...]
>>
>> +       while ((p = *tail) != NULL) {
>>
>> Because we can.
>
> Be reasonable.  You cannot sensibly rewrite it to
>
> 	p = *tail;
>         while (p) {
>         	...
> 		p = *tail;
> 	}
>
> when you do not know how ... part would evolve in the future.

The only unknown here is the potential presence of "continue;" in ... and that can be addressed by writing

    for (p = *tail; p; p = *tail) {
       ...
    }

However, that only makes sense where ... is rather large and diverse and the assignment in question provides a unifying point. In this case, the loop is rather small and perfectly fits on one screen. It turns out that the assignment only serves for _obfuscating_ the various code paths. We have:

        while ((p = *tail) != NULL) {
                cmp = ((i >= q->nr)
                       ? -1 : strcmp(p->path, q->queue[i]->two->path));
                if (cmp < 0) {
                        /* p->path not in q->queue[]; drop it */
                        *tail = p->next;
                        free(p);
                        continue;
                }
                if (cmp > 0) {
                        /* q->queue[i] not in p->path; skip it */
                        i++;
                        continue;
                }
                hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1);
                p->parent[n].mode = q->queue[i]->one->mode;
                p->parent[n].status = q->queue[i]->status;
                tail = &p->next;
                i++;
        }
While we could instead have:
      p = curr;
      while (p) {
                cmp = ((i >= q->nr)
                       ? -1 : strcmp(p->path, q->queue[i]->two->path));
                if (cmp < 0) {
                        struct combine_diff_path *n = p->next;
                        /* p->path not in q->queue[]; drop it */
                        free(p);
                        p = *tail = n;
                        continue;
                }
                if (cmp > 0) {
                        /* q->queue[i] not in p->path; skip it */
                        i++;
                        continue;
                }
                hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1);
                p->parent[n].mode = q->queue[i]->one->mode;
                p->parent[n].status = q->queue[i]->status;
                p = *(tail = &p->next);
                i++;
        }

Of course, it only makes limited sense to recheck p after the second if, so it would be clearer to write

      p = curr;
      while (p) {
                cmp = ((i >= q->nr)
                       ? -1 : strcmp(p->path, q->queue[i]->two->path));
                if (cmp < 0) {
                        struct combine_diff_path *n = p->next;
                        /* p->path not in q->queue[]; drop it */
                        free(p);
                        p = *tail = n;
                        continue;
                }
                if (cmp > 0) {
                        /* q->queue[i] not in p->path; skip it */
                        i++;
                        continue;
                }
                hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1);
                p->parent[n].mode = q->queue[i]->one->mode;
                p->parent[n].status = q->queue[i]->status;
                p = *(tail = &p->next);
                i++;
        }

But that's sort of a red herring since the actual loop structure is hidden in conditions where it does not belong. (i >= q->nr) is a _terminal_ condition.

So it's more like
    p = curr;
    while (p) {
        if (i >= q->nr) {
            *tail = NULL;
            do {
                struct combine_diff_path *n = p->next;
                free(p);
                p = n;
            } while (p);
            break;
        }
        cmp = strcmp(p->path, q->queue[i]->two->path));
        if (cmp < 0) {
                struct combine_diff_path *n = p->next;
                /* p->path not in q->queue[]; drop it */
                free(p);
                p = *tail = n;
                continue;
        }
        if (cmp == 0) {
                hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1);
                p->parent[n].mode = q->queue[i]->one->mode;
                p->parent[n].status = q->queue[i]->status;
                p = *(tail = &p->next);
        }
        i++;
    }
> 	if ((p = *tail) != NULL) {
>         	...
>
> is a totally different issue.
Yes: it was just a matter of style instead of preventing _other_ code to
be rewritten in a clearer manner.
For a "don't look elsewhere" solution,
        while ((p = *tail) != NULL)
can _always_ be equivalently replaced with
        for (p = *tail; p; p = *tail)
and in this case already trivially improved with
        for (p = curr; p; p = *tail)

which meets your style prescription "avoid assignments in conditionals when you do not have to." but in this particular case, the "don't look elsewhere" solution was not called for. It unifies code paths that deserve to stay separate: we don't _want_ the assignment to take place for every path leading to the loop control. It makes it less clear to see what happens.

-- 
David Kastrup
Previous: Junio C Hamano
Message 23 of 23 in “blame.c: prepare_lines should not call xrealloc for every line”
  1. blame.c: prepare_lines should not call xrealloc for every lineDavid Kastrup, Feb 4, 2014
  2. David KastrupFeb 4, 2014
  3. Junio C HamanoFeb 4, 2014
  4. Junio C HamanoFeb 4, 2014
  5. David KastrupFeb 4, 2014
  6. Philip OakleyFeb 4, 2014
  7. Philip OakleyFeb 4, 2014
  8. Junio C HamanoFeb 4, 2014
  9. David KastrupFeb 4, 2014
  10. Junio C HamanoFeb 4, 2014
  11. David KastrupFeb 4, 2014
  12. Junio C HamanoFeb 4, 2014
  13. David KastrupFeb 4, 2014
  14. Junio C HamanoFeb 4, 2014
  15. David KastrupFeb 4, 2014
  16. Junio C HamanoFeb 4, 2014
  17. David KastrupFeb 5, 2014
  18. Junio C HamanoFeb 5, 2014
  19. David KastrupFeb 6, 2014
  20. David KastrupFeb 6, 2014
  21. David KastrupFeb 5, 2014
  22. Junio C HamanoFeb 5, 2014
  23. David KastrupFeb 5, 2014

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.