Skip to content

should there be a strcat() here? #89

@enh-google

Description

@enh-google

arm64 is the only architecture Android supports that doesn't have an optimized strcat(). i'm curious whether the absence of a strcat() in this project is because arm's assumption is that the fallback of strcpy(dst + strlen(dst), src) is good enough, given that both strcpy() and strlen() do have optimized variants?

it turns out it's not a good assumption that that's everyone's fallback...

musl and glibc do both have the obvious strcpy() + strlen() implementation (so you're only paying for the two function calls there).

Android's x86 optimized routines from intel are literally #includes of their optimized strlen() and strcpy() to inline the implementations and avoid two function calls. the arm32 and riscv64 assembler routines are hand-inlined strlen()+strcpy().

netbsd has assembler ... that calls strlen() and strcpy() (for arm32 and arm64; x86/x86-64 are inlined).

freebsd and openbsd (and thus bionic) have a simple c implementation and so don't benefit from any strlen()/strcpy() optimizations:

char *
strcat(char *s, const char *append)
{
	char *save = s;

	for (; *s; ++s);
	while ((*s++ = *append++) != '\0');
	return(save);
}

(i don't know whether freebsd/openbsd build with autovectorization for arm64 that might make this better, but android doesn't. openbsd's git history shows this file hasn't been meaningfully touched since the 1995 fork from netbsd.)

apple's fork of FreeBSD's code has this instead, which is a unique [afaik] variant with not one but two strlen() calls and a memcpy():

char *
strcat(char *restrict dst, const char *restrict src) {
    const size_t dstlen = strlen(dst);
    const size_t srclen = strlen(src);
    //  The strcat() and strncat() functions append a copy of the null-
    //  terminated string src to the end of the null-terminated string dst,
    //  then add a terminating '\0'.  The string dst must have sufficient
    //  space to hold the result.
    memcpy(dst+dstlen, src, srclen+1);
    //  The strcat() and strncat() functions return dst.
    return dst;
}

and not that i'm aware of anyone shipping this code yet, but llvm-libc has another unique variant, reusing their strlen() but not their strcpy():

LLVM_LIBC_FUNCTION(char *, strcat,
                   (char *__restrict dest, const char *__restrict src)) {
  LIBC_CRASH_ON_NULLPTR(dest);
  LIBC_CRASH_ON_NULLPTR(src);
  size_t dest_length = internal::string_length(dest);
  size_t i;
  for (i = 0; src[i] != '\0'; ++i)
    dest[dest_length + i] = src[i];

  dest[dest_length + i] = '\0';
  return dest;
}

i'll send this link around and try to find out whether anyone did any benchmarking (and if so, "of what?") before coming to their various choices...

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions