Return Styles: Pseud0ch, Terminal, Valhalla, NES, Geocities, Blue Moon. Entire thread

C and goto

Name: Anonymous 2012-06-19 6:00

Is it okay to use goto in C for functions like this? (It's actually C++, but I'm doing C-style development.) Is this function too long, or is it okay due to the nature of null-terminated string processing? It's been a long time since I've done C-style development. Any other recommendations (except "use Lisp or XYZ language instead")?

ssize_t normalize_path(char* restrict dest, size_t dest_max, char const* restrict p, size_t n) noexcept {
    size_t i, m, length;
    ssize_t retval;
    char const* q;
    char* buffer, * state;
    char const** parts, **new_parts;
    size_t parts_size, new_parts_size;
    char const* default_parts[32];

    // validate arguments
    if ((dest_max > (SSIZE_MAX + 1)) || (!dest && dest_max) || (!p && n)) {
        errno = EINVAL;
        return -1;
    }

    length = 0;

    if (!n) {
        // empty path, normalize to current directory
        goto normalize_curdir;
    }
       
    if (*p == '/') {
        // POSIX paths may begin with one or two slashes, but three or more
        // are treated as a single slash
        ++length; ++p; --n;
        if (n && (*p == '/')) {
            ++p; --n;
            q = strncchr(p, '/', n);
            if (!q) {
                q = p + n;
            }

            if (q == p) {
                ++length;
            }
            else {
                n -= q - p;
                p = q;
            }
        }

        // copy one or two slashes into destination buffer
        for (i = 0, m = (length < dest_max) ? length : dest_max - 1; i < m; ++i) {
            dest[i] = '/';
        }

        if (!n) {
            goto null_terminate;
        }
    }

    // create a local copy of input path for tokenization
    buffer = static_cast<char*>(malloca(n + 1));
    if (!buffer) {
        return -1;
    }

    *static_cast<char*>(mempcpy(buffer, p, n)) = '\0';

    // tokenize the local path and normalize parts into stack
    i = 0;
    retval = 0;
    state = nullptr;
    parts = default_parts;
    parts_size = sizeof(default_parts) / sizeof(default_parts[0]);

    for (q = strtok_r(buffer, path<char>::sepset, &state); q; q = strtok_r(nullptr, path<char>::sepset, &state)) {
        // skip part if curdir, pop top part off stack if pardir, and if
        // the path is absolute (length is non-zero), eat all of the
        // redundant pardir parts
        if (*q == '.') {
            if (q[1] == '\0') {
                continue;
            }
            else if ((q[1] == '.') && (q[2] == '\0')) {
                if (i > 0) {
                    --i;
                    continue;
                }
                else if (length > 0) {
                    continue;
                }
            }
        }

        // resize the path parts stack if space is exhausted
        if (i >= parts_size) {
            if (parts_size >= (SIZE_MAX / (3 * sizeof(char const*)))) {
                errno = ENOMEM;
                retval = -1;
                goto cleanup;
            }

            new_parts_size = (parts_size * 3) / 2;
            new_parts = static_cast<char const**>(malloc(new_parts_size * sizeof(char const*)));
            if (!new_parts) {
                retval = -1;
                goto cleanup;
            }

            memcpy(new_parts, parts, parts_size * sizeof(char const*));
            if (parts != default_parts) {
                free(parts);
            }

            parts = new_parts;
            parts_size = new_parts_size;
        }

        // push part onto stack
        parts[i++] = q;
    }

    // rejoin the path parts in normalized form
    if (length < dest_max) {
        retval = join_path(dest + length, dest_max - length, parts, i);
    }
    else {
        retval = join_path(nullptr, 0, parts, i);
    }

cleanup:

    // free temporary buffers
    if (parts != default_parts) {
        free(parts);
    }

    freea(buffer);

    // update length with full length from join
    if (retval < 0) {
        return -1;
    }

    length += static_cast<size_t>(retval);
    if (!length) {
        goto normalize_curdir;
    }
    else if (length > SSIZE_MAX) {
        errno = EOVERFLOW;
        return -1;
    }

    return static_cast<ssize_t>(length);

normalize_curdir:

    // empty path, normalize to current directory
    UP_ASSERT(!length);
    ++length;
    if (dest_max > 0) {
        dest[0] = '.';
    }

null_terminate:

    // null terminate the destination buffer
    if (length < dest_max) {
        dest[length] = '\0';
    }
    else if (dest_max > 0) {
        dest[dest_max - 1] = '\0';
    }

    return static_cast<ssize_t>(length);
}

Name: Anonymous 2012-06-19 9:55

I hate it, when in C, the code execution implicitily goes to next line, so I always explicitily write where the execution should go.

I also never create other functions that `main`, since calling them is so slow. I've developed a naming convention, where every line has label like [PROC NAME]_[LOCAL LINE NUMBER]. This is very efficient in keeping the code simple (yeah, I think following KISS principle is good).

In my opinion, this results in extremely easy code to read. There is never need for guessin where the execution goes next. Always expicit goto.

Here's an example.

greet_L1: char buf[16]; goto L2;
greet_L2: puts("Who are you?"); goto L3;
greet_L3: gets(buf); goto L4;
greet_L4: printf("Hello "); goto L5;
greet_L5: printf(buf); goto L6;
greet_L6: goto exit_L1;
exit_L1: return 0;

Newer Posts
Don't change these.
Name: Email:
Entire Thread Thread List