strlen old bottle new wine

Keywords: C glibc Programming less Attribute

Introduction - overview of strlen

I accidentally scan the code in glibc strlen.c, which I can't forget for a long time. I also remember the starting point in my unknown programming career:

Programming is not a joke, some are difficult, some are reluctant to give up. Review with the track, once the most familiar feeling of strlen~

/* Copyright (C) 1991-2020 Free Software Foundation, Inc.
   This file is part of the GNU C Library.
   Written by Torbjorn Granlund (tege@sics.se),
   with help from Dan Sahlin (dan@sics.se);
   commentary by Jim Blandy (jimb@ai.mit.edu).

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, see
   <https://www.gnu.org/licenses/>.  */

#include <string.h>
#include <stdlib.h>

#undef strlen

#ifndef STRLEN
# define STRLEN strlen
#endif

/* Return the length of the null-terminated string STR.  Scan for
   the null terminator quickly by testing four bytes at a time.  */
size_t
STRLEN (const char *str)
{
  const char *char_ptr;
  const unsigned long int *longword_ptr;
  unsigned long int longword, himagic, lomagic;

  /* Handle the first few characters by reading one character at a time.
     Do this until CHAR_PTR is aligned on a longword boundary.  */
  for (char_ptr = str; ((unsigned long int) char_ptr
            & (sizeof (longword) - 1)) != 0;
       ++char_ptr)
    if (*char_ptr == '\0')
      return char_ptr - str;

  /* All these elucidatory comments refer to 4-byte longwords,
     but the theory applies equally well to 8-byte longwords.  */

  longword_ptr = (unsigned long int *) char_ptr;

  /* Bits 31, 24, 16, and 8 of this number are zero.  Call these bits
     the "holes."  Note that there is a hole just to the left of
     each byte, with an extra at the end:

     bits:  01111110 11111110 11111110 11111111
     bytes: AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD

     The 1-bits make sure that carries propagate to the next 0-bit.
     The 0-bits provide holes for carries to fall into.  */
  himagic = 0x80808080L;
  lomagic = 0x01010101L;
  if (sizeof (longword) > 4)
    {
      /* 64-bit version of the magic.  */
      /* Do the shift in two steps to avoid a warning if long has 32 bits.  */
      himagic = ((himagic << 16) << 16) | himagic;
      lomagic = ((lomagic << 16) << 16) | lomagic;
    }
  if (sizeof (longword) > 8)
    abort ();

  /* Instead of the traditional loop which tests each character,
     we will test a longword at a time.  The tricky part is testing
     if *any of the four* bytes in the longword in question are zero.  */
  for (;;)
    {
      longword = *longword_ptr++;

      if (((longword - lomagic) & ~longword & himagic) != 0)
    {
      /* Which of the bytes was the zero?  If none of them were, it was
         a misfire; continue the search.  */

      const char *cp = (const char *) (longword_ptr - 1);

      if (cp[0] == 0)
        return cp - str;
      
      if (cp[1] == 0)
        return cp - str + 1;
      if (cp[2] == 0)
        return cp - str + 2;
      if (cp[3] == 0)
        return cp - str + 3;
      if (sizeof (longword) > 4)
        {
          if (cp[4] == 0)
        return cp - str + 4;
          if (cp[5] == 0)
        return cp - str + 5;
          if (cp[6] == 0)
        return cp - str + 6;
          if (cp[7] == 0)
        return cp - str + 7;
        }
    }
    }
}
libc_hidden_builtin_def (strlen)

 

Body - thinking and analysis

1. How large are the unsigned long int bytes? 4 bytes, 8 bytes

  unsigned long int longword, himagic, lomagic;

 

For example, most Linux, x86 sizeof (long) = 4, x64 sizeof (long) = 8

window x86, x64 sizeof (long) = 4. (May 28, 2020), C standard guarantee sizeof (long) > = sizeof (int)

How many bytes are given to the implementer

 

2. ((unsigned long int) char_ PTR & (sizeof (longword) - 1)) bit alignment

  /* Handle the first few characters by reading one character at a time.
     Do this until CHAR_PTR is aligned on a longword boundary.  */
  for (char_ptr = str; ((unsigned long int) char_ptr
            & (sizeof (longword) - 1)) != 0;
       ++char_ptr)
    if (*char_ptr == '\0')
      return char_ptr - str;

 

The purpose of the initial code is to let chart_ptr performs bit alignment according to the size of sizeof (unsigned long) bytes

This involves many computer hardware alignment requirements and performance considerations (performance is the main factor)

 

3. himagic = 0x80808080L; lomagic = 0x01010101L; what fuck ? 

  /* Bits 31, 24, 16, and 8 of this number are zero.  Call these bits
     the "holes."  Note that there is a hole just to the left of
     each byte, with an extra at the end:

     bits:  01111110 11111110 11111110 11111111
     bytes: AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD

     The 1-bits make sure that carries propagate to the next 0-bit.
     The 0-bits provide holes for carries to fall into.  */
  himagic = 0x80808080L;
  lomagic = 0x01010101L;
  if (sizeof (longword) > 4)
    {
      /* 64-bit version of the magic.  */
      /* Do the shift in two steps to avoid a warning if long has 32 bits.  */
      himagic = ((himagic << 16) << 16) | himagic;
      lomagic = ((lomagic << 16) << 16) | lomagic;
    }
  if (sizeof (longword) > 8)
    abort ();

  /* Instead of the traditional loop which tests each character,
     we will test a longword at a time.  The tricky part is testing
     if *any of the four* bytes in the longword in question are zero.  */
  for (;;)
    {
      longword = *longword_ptr++;

      if (((longword - lomagic) & ~longword & himagic) != 0)
    {

 

3.1 (((longword - lomagic) & ~longword & himagic) != 0) ? mmp ?

Maybe this is art. It's a genius to think of this idea. It's so ingenious. Hahaha. We'll explain it in two small points

For the first time, it's a bit cute. I'll use a simple idea to help you understand this problem. The above code mainly focuses on

sizeof (unsigned long) 4-byte and 8-byte are obtained by processing. We can simply process 1-byte by analogy with recursion mechanism

Understand the principle behind this formula

/**
 * himagic      : 1000 0000
 * lomagic      : 0000 0001
 * longword     : XXXX XXXX
 * /
unsigned long himagic = 0x80L;
unsigned long lomagic = 0x01L;

unsigned long longword ;

Then we carefully analyze the following formula

((longword - lomagic) & ~longword & himagic)

(& himagic) = (& 10 million) indicates that in the end only the highest level is concerned

Discussion on three cases of long word

longword     : 1XXX XXXX  128 =< x <= 255
longword     : 0XXX XXXX  0 < x < 128
longword     : 0000 0000  x = 0

The first type of longword = 1XXX XXXX

So ~ longword = 0yyyyyy obviously ~ longword & himagic = 0000 0000 don't continue

The second type of longword = 0xxxx XXXX and not less than 0, and not less than 1

Obviously (longword - lomagic) = 0zzz zzz > = 0 and < 127, because lomagic = 1

At this moment (longword - lomagic) & himagic = 0zzz zzz & 100000000 = 0, so there is no need to continue

The third type of longword = 0000 0000

Then ~ longword & himagic = 1111 1111 & 1000 0000 = 1000 000;

Look at (longword - lomagic) = (0000 0000 - 0000 0001), because the unsigned number subtraction is based on

(complement (0000 0000) + complement (- 000 0001)) = (complement (0000 0000) + complement (~ 000 0001 + 1))

= (complement (0000 0000) + complement (1111111 1111)) = 1111 1111 (quick formula can be used to get the final result),

So the final result is 1111 1111 & 1000 0000 = 1000 0000 > 0

Through comprehensive discussion, we can skillfully screen out whether the value is 0 according to the above formula. For 2 bytes, 4 bytes and 8 bytes, the idea is completely similar

 

3.2 (sizeof (long word) > 4)? (sizeof (long word) > 8) why don't you use macros to make great achievements?

The macro can share the source code of multiple platforms, but not the binary system of multiple platforms. glibc is a general project, and its portability affects factors

It may be very heavy

 

4. libc_hidden_builtin_def (strlen)~

To understand this, we need to introduce some off-site information (different compilation parameters will be different, only one branch solution is extracted here)

// file : glibc-2.31/include/libc-symbols.h

libc_hidden_builtin_def (strlen)

#define libc_hidden_builtin_def(name) libc_hidden_def (name)

# define libc_hidden_def(name) hidden_def (name)

/* Define ALIASNAME as a strong alias for NAME.  */
# define strong_alias(name, aliasname) _strong_alias(name, aliasname)
# define _strong_alias(name, aliasname) \
  extern __typeof (name) aliasname __attribute__ ((alias (#name))) \
    __attribute_copy__ (name);

/* For assembly, we need to do the opposite of what we do in C:
   in assembly gcc __REDIRECT stuff is not in place, so functions
   are defined by its normal name and we need to create the
   __GI_* alias to it, in C __REDIRECT causes the function definition
   to use __GI_* name and we need to add alias to the real name.
   There is no reason to use hidden_weak over hidden_def in assembly,
   but we provide it for consistency with the C usage.
   hidden_proto doesn't make sense for assembly but the equivalent
   is to call via the HIDDEN_JUMPTARGET macro instead of JUMPTARGET.  */
#  define hidden_def(name)    strong_alias (name, __GI_##name)

/* Undefine (also defined in libc-symbols.h).  */
#undef __attribute_copy__
#if __GNUC_PREREQ (9, 0)
/* Copies attributes from the declaration or type referenced by
   the argument.  */
# define __attribute_copy__(arg) __attribute__ ((__copy__ (arg)))
#else
# define __attribute_copy__(arg)
#endif

 

Use the macro definition above to expand

libc_hidden_builtin_def (strlen)
|

hidden_def (strlen)
|

strong_alias (strlen, __GI_strlen)
|

_strong_alias (strlen, __GI_strlen)
|

extern __typeof (strlen) __GI_strlen __attribute__ ((alias ("strlen"))) __attribute_copy__ (strlen);
|
extern __typeof (strlen) __GI_strlen __attribute__ ((alias ("strlen"))) __attribute__ ((__copy__ (strlen))); ``

 

Where GUN C extended syntax

  __ typeof (arg): gets the declared type of the variable
  __ attribute__ ((__ copy__ (ARG)): attribute copy replication attribute above GCC 9
  alias_name __attribute__ ((alias (name))): declare the symbol alias alias alias name for name
 
Summary: libc_hidden_builtin_def (strlen) means to redefine a symbol alias based on the strlen symbol__ GI_strlen. 
(supplementary information strong_alias notes)
 
 
There are many kinds of strlen engineering codes. Here we choose a general glibc version to think and analyze. If you are interested, please refer to more
Come here as you like. Be happy is the most important thing. Train Rui Chenggang. Hahaha

 

Postscript - outlook and life

Mistakes are inevitable. Please correct and communicate

Posted by jtrost on Fri, 29 May 2020 06:52:45 -0700