Neden hızlı çalışabilmek için glibc strlen'in bu kadar karmaşık olması gerekiyor?


286

Buradastrlen kodu bakıyordum ve kodda kullanılan optimizasyonların gerçekten gerekli olup olmadığını merak ediyordum? Örneğin, aşağıdakine benzer bir şey neden eşit derecede iyi veya daha iyi çalışmaz?

unsigned long strlen(char s[]) {
    unsigned long i;
    for (i = 0; s[i] != '\0'; i++)
        continue;
    return i;
}

Daha basit kod, derleyicinin optimize etmesi için daha iyi ve / veya daha kolay değil mi?

Bağlantının strlenarkasındaki sayfadaki kodu şöyle görünür:

/* Copyright (C) 1991, 1993, 1997, 2000, 2003 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, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

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

#undef strlen

/* 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 (str)
     const char *str;
{
  const char *char_ptr;
  const unsigned long int *longword_ptr;
  unsigned long int longword, magic_bits, 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.  */
  magic_bits = 0x7efefeffL;
  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.  */
      magic_bits = ((0x7efefefeL << 16) << 16) | 0xfefefeffL;
      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 (;;)
    {
      /* We tentatively exit the loop if adding MAGIC_BITS to
     LONGWORD fails to change any of the hole bits of LONGWORD.

     1) Is this safe?  Will it catch all the zero bytes?
     Suppose there is a byte with all zeros.  Any carry bits
     propagating from its left will fall into the hole at its
     least significant bit and stop.  Since there will be no
     carry from its most significant bit, the LSB of the
     byte to the left will be unchanged, and the zero will be
     detected.

     2) Is this worthwhile?  Will it ignore everything except
     zero bytes?  Suppose every byte of LONGWORD has a bit set
     somewhere.  There will be a carry into bit 8.  If bit 8
     is set, this will carry into bit 16.  If bit 8 is clear,
     one of bits 9-15 must be set, so there will be a carry
     into bit 16.  Similarly, there will be a carry into bit
     24.  If one of bits 24-30 is set, there will be a carry
     into bit 31, so all of the hole bits will be changed.

     The one misfire occurs when bits 24-30 are clear and bit
     31 is set; in this case, the hole at bit 31 is not
     changed.  If we had access to the processor carry flag,
     we could close this loophole by putting the fourth hole
     at bit 32!

     So it ignores everything except 128's, when they're aligned
     properly.  */

      longword = *longword_ptr++;

      if (
#if 0
      /* Add MAGIC_BITS to LONGWORD.  */
      (((longword + magic_bits)

        /* Set those bits that were unchanged by the addition.  */
        ^ ~longword)

       /* Look at only the hole bits.  If any of the hole bits
          are unchanged, most likely one of the bytes was a
          zero.  */
       & ~magic_bits)
#else
      ((longword - lomagic) & himagic)
#endif
      != 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)

Bu sürüm neden hızlı çalışıyor?

Çok fazla gereksiz iş yapmıyor mu?


2
Yorumlar uzun tartışmalar için değildir; bu görüşme sohbete taşındı .
Samuel Liew

18
İleride başvurmak üzere GNU libc için resmi kaynak deposu < sourceware.org/git/?p=glibc.git > adresindedir . < sourceware.org/git/?p=glibc.git;a=blob;f=string/… > gerçekten yukarıdakine benzer bir kod gösterir; bununla birlikte, sysdepsglibc'nin desteklenen mimarilerinin çoğunda (yerine en sık kullanılan mimari MIPS), dizinden elle yazılmış bir montaj dili uygulaması kullanılacaktır.
zwol

9
Bunu öncelikle görüş temelli olarak kapatmak için oy vermek; "XXX'de xxx gerçekten gerekli mi?" insanların görüşlerine özneldir.
SS Anne

2
@ JL2210: İyi bir nokta, sorunun ruhunu yakalamak için başlığın düzeltilmesi, başlığın performansın gerekip gerekmediğini merak ediyor gibi görünmüyor, sadece performans için bu optimizasyonlara neden ihtiyacımız var .
Peter Cordes

9
@ JL2210 FWIW, orijinal başlık "C [sic!] 'De neden bu kadar karmaşık?" İdi ve "çok geniş" olarak kapatıldı, sonra yeniden açıldı, sonra "öncelikle görüş tabanlı" olarak kapatıldı. Bunu düzeltmeye çalıştım (bu arada "sorumu kırdın!" Ve "sizler düzenleme güçlerinizi kötüye kullanıyorsunuz!"), Ancak IMVHO, sorunun temel öncülünde yalan söyledi (ve hala yatıyor), sorunlu ("bu kod anlamak için çok karmaşık" Q & A için uygun değil - IMO bir cevap için değil, bir ders için istektir). Ben 60 metrelik bir kutup ile tekrar

Yanıtlar:


233

İhtiyacınız yoktur ve asla böyle bir kod yazmamalısınız - özellikle bir C derleyicisi / standart kütüphane satıcısı değilseniz. strlenBazı çok şüpheli hız korsanlığı ve varsayımları ile uygulamak için kullanılan koddur (iddialarla test edilmeyen veya yorumlarda belirtilmeyen):

  • unsigned long 4 veya 8 bayttır
  • bayt 8 bittir
  • bir işaretçi için dökülebilir unsigned long longveuintptr_t
  • sadece 2 veya 3 en düşük sıra bitinin sıfır olduğunu kontrol ederek imleci hizalayabilir
  • bir dizeye unsigned longs olarak erişebilir
  • herhangi bir hasta etkisi olmadan dizinin sonunu okuyabilir.

Dahası, iyi bir derleyici,

size_t stupid_strlen(const char s[]) {
    size_t i;
    for (i=0; s[i] != '\0'; i++)
        ;
    return i;
}

(bununla uyumlu bir tür olması gerektiğini unutmayın size_t) derleyici yerleşik bir inline sürümü ile strlenveya kodu vectorize; ancak bir derleyicinin karmaşık sürümü optimize etme olasılığı düşüktür.


strlenFonksiyon ile tarif edilir C11 7.24.6.3 olarak:

Açıklama

  1. strlenFonksiyon s ile gösterilen dize uzunluğunu hesaplar.

İadeler

  1. strlenFonksiyon Sonlandırıcı null karakterin önüne karakter sayısını döndürür.

Dize tarafından işaret Şimdi, eğer ssadece uzun dize içerecek kadar ve sonlandırma boş karakterlerden dizideki oldu davranış olacaktır tanımsız biz örneğin, null Terminatör geçmiş dize erişmek eğer

char *str = "hello world";  // or
char array[] = "hello world";

Tamamen taşınabilir / standartlarla uyumlu C'de bunu doğru bir şekilde uygulamak için gerçekten tek yol, önemsiz dönüşümler hariç , sorunuzda yazılma şeklidir - döngüyü vb. Açarak daha hızlı gibi davranabilirsiniz, ancak yine de yapılması gerekir. her seferinde bir bayt .

(Yorumcuların belirttiği gibi, katı taşınabilirlik çok fazla bir yük olduğunda, makul veya bilinen güvenli varsayımlardan yararlanmak her zaman kötü bir şey değildir. Özellikle belirli bir C uygulamasının parçası olan kodda . nasıl / ne zaman bükeceğinizi bilmeden önce kurallar.)


Bağlantılı strlenuygulama önce işaretçi doğal 4 veya 8 bayt hizalama sınırını gösterene kadar baytları ayrı ayrı kontrol eder unsigned long. C standardı, düzgün bir şekilde hizalanmayan bir işaretçiye erişmenin tanımlanmamış bir davranışa sahip olduğunu söylüyor , bu yüzden bir sonraki kirli hilenin daha da kirli olması için bu kesinlikle yapılmalıdır. (X86 dışındaki bazı CPU mimarisi Uygulamada, bir yanlış hizalanmış kelime veya çiftsözcük yük C'dir. Arıza edecek değil taşınabilir montaj dil, ancak bu kod bu şekilde kullanıyor). Ayrıca, bellek korumasının hizalanmış bloklarda (örn. 4kiB sanal bellek sayfaları) çalıştığı uygulamalarda hata yapma riski olmadan bir nesnenin sonunu okumayı mümkün kılan da budur.

Kod: Artık kirli kısım geliyor sonları söz ve 4 veya 8 8 bitlik bir zaman (bir de bayt okur long intolsaydı hızla anlamaya) ve işaretsiz eklenmesiyle biraz hile kullanan herhangi olanlar 4 veya 8 içinde sıfır bayt bayt - taşıma bitinin bir bit maskesi tarafından yakalanan bitleri değiştirmesine neden olacak özel hazırlanmış bir sayı kullanır. Özünde bu, maskedeki 4 veya 8 bayttan herhangi birinin, bu baytların her biri boyunca döngüden daha hızlı sıfırlar olup olmadığını anlayacaktır. Son olarak, hangi baytın (eğer varsa) ilk sıfır olduğunu bulmak ve sonucu döndürmek için bir döngü vardır .

En büyük sorun, sizeof (unsigned long) - 1zaman zaman sizeof (unsigned long), dizenin sonundan sonra okunmasıdır - yalnızca boş bayt son erişilen bayttaysa (yani küçük endianda en anlamlı ve büyük endianda en az anlamlı) bu mu değil aut dizisine erişmek!


Kod, strlenbir C standart kitaplığında uygulamak için kullanılmasına rağmen hatalı koddur. İçinde birçok uygulama tanımlı ve tanımlanmamış yönleri vardır ve sistem tarafından sağlanan yerine hiçbir yerde kullanılmamalıdır strlen- İşlevi the_strlenburada yeniden adlandırdım ve aşağıdakileri ekledim main:

int main(void) {
    char buf[12];
    printf("%zu\n", the_strlen(fgets(buf, 12, stdin)));
}

Tampon dikkatlice boyutlandırılır, böylece tam olarak hello worldipi ve sonlandırıcıyı tutabilir . Ancak 64-bit işlemcimde unsigned long8 bayt var, bu nedenle ikinci kısma erişim bu arabelleği aşacak.

Şimdi derleme -fsanitize=undefinedve -fsanitize=addressortaya çıkan program ile çalıştırmak, elde:

% ./a.out
hello world
=================================================================
==8355==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffffe63a3f8 at pc 0x55fbec46ab6c bp 0x7ffffe63a350 sp 0x7ffffe63a340
READ of size 8 at 0x7ffffe63a3f8 thread T0
    #0 0x55fbec46ab6b in the_strlen (.../a.out+0x1b6b)
    #1 0x55fbec46b139 in main (.../a.out+0x2139)
    #2 0x7f4f0848fb96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
    #3 0x55fbec46a949 in _start (.../a.out+0x1949)

Address 0x7ffffe63a3f8 is located in stack of thread T0 at offset 40 in frame
    #0 0x55fbec46b07c in main (.../a.out+0x207c)

  This frame has 1 object(s):
    [32, 44) 'buf' <== Memory access at offset 40 partially overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (.../a.out+0x1b6b) in the_strlen
Shadow bytes around the buggy address:
  0x10007fcbf420: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007fcbf430: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007fcbf440: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007fcbf450: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007fcbf460: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10007fcbf470: 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00[04]
  0x10007fcbf480: f2 f2 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007fcbf490: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007fcbf4a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007fcbf4b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007fcbf4c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==8355==ABORTING

yani kötü şeyler oldu.


120
Re: "çok şüpheli hız kesmek ve varsayımlar" - yani, taşınabilir kod çok şüpheli . Standart kütüphane, belirli bir derleyici / donanım kombinasyonu için, dil tanımının tanımsız olarak bıraktığı şeylerin gerçek davranışları hakkında bilgi sahibi olarak yazılmıştır. Evet, çoğu insan böyle bir kod yazmamalıdır, ancak taşınabilir olmayan standart kütüphaneyi uygulama bağlamında doğal olarak kötü değildir.
Pete Becker

4
Katılıyorum, asla böyle şeyler yazmayın. Ya da neredeyse hiç. Erken optimizasyon tüm kötülüklerin kaynağıdır. (Bu durumda aslında motive edilebilir). Aynı uzun dizede çok sayıda strlen () çağrısı yaparsanız, uygulamanız belki de farklı yazılabilir. Örneğin, dize oluşturulduğunda dize uzunluğunu zaten bir değişkene kaydedersiniz ve strlen () işlevini çağırmanız gerekmez.
ghellquist

65
@ghellquist: Sık kullanılan bir kütüphane çağrısını optimize etmek neredeyse "erken optimizasyon" değildir.
jamesqf

7
@Antti Haapala: Kesinlikle neden stlenin O (1) olması gerektiğini düşünüyorsun? Ve burada sahip olduğumuz, hepsi O (n) olan, ancak farklı sabit çarpanları olan birkaç uygulama. Bunun önemli olduğunu düşünmeyebilirsiniz, ancak bazılarımız için, çalışmasını mikrosaniye içinde yapan bir O (n) algoritmasının uygulanması, saniye veya milisaniye süren bir işlemden çok daha iyidir, çünkü bir işin seyri.
jamesqf

8
@PeteBecker: sadece standart kütüphaneler bağlamında (bu durumda çok fazla değil) taşınabilir bir kod yazmanın standart bir kütüphanenin amacı belirli şeyler için standart bir arayüz sağlamak olduğundan norm olabilir.
PlazmaHH

148

Bunun için bazı ayrıntılar / arkaplan hakkında yorumlarda (biraz veya tamamen) yanlış tahminler var.

Glibc'in optimize edilmiş C geri dönüşü optimize edilmiş uygulamasına bakıyorsunuz . (Elle yazılmış bir asm uygulaması olmayan ISA'lar için) . Veya bu kodun hala glibc kaynak ağacında bulunan eski bir sürümü. https://code.woboq.org/userspace/glibc/string/strlen.c.html , mevcut glibc git ağacına dayanan bir kod tarayıcıdır. Görünüşe göre, MIPS de dahil olmak üzere birkaç ana glibc hedefi tarafından kullanılıyor. (Teşekkürler @zwol).

X86 ve ARM gibi popüler ISA'larda glibc elle yazılmış bir asm kullanır

Bu nedenle, bu kodla ilgili herhangi bir şeyi değiştirme teşviği düşündüğünüzden daha düşüktür.

Bu bithack kodu ( https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord ) aslında sunucunuzda / masaüstü / dizüstü bilgisayarınızda / akıllı telefonunuzda çalışan şey değildir. Bir seferde saf bir bayt döngüden daha iyidir, ancak bu bithack bile modern CPU'lar için verimli bir asm ile karşılaştırıldığında oldukça kötüdür (özellikle AVX2 SIMD'nin 32 baytı birkaç talimatla kontrol etmesine izin verir, saatte 32 ila 64 bayt sağlar. 2 / saat vektör yükü ve ALU verimi olan modern CPU'larda L1d önbelleğinde veriler sıcaksa ana döngüde döngü. yani başlangıç ​​ek yükünün baskın olmadığı orta boyutlu dizeler için.)

glibc, CPU'nuz için strlenen uygun sürüme çözümlemek için dinamik bağlantı hileleri kullanır , bu nedenle x86'da bile bir SSE2 sürümü (16 bayt vektör, x86-64 için taban çizgisi) ve AVX2 sürümü (32 bayt vektör) bulunur.

x86, vektör ve genel amaçlı kayıtlar arasında verimli veri aktarımına sahiptir, bu da döngü kontrolünün verilere bağlı olduğu örtülü uzunluklu dizelerdeki fonksiyonları hızlandırmak için SIMD'nin kullanılmasını benzersiz kılar (?). pcmpeqb/ pmovmskbbir seferde 16 ayrı baytın test edilmesini mümkün kılar.

glibc, AdvSIMD kullanarak buna benzer bir AArch64 sürümüne ve vector-> GP kayıtlarının boru hattını durdurduğu AArch64 CPU'lara yönelik bir sürümüne sahiptir, bu nedenle bu bithack'i kullanır . Ancak, bir kez isabet aldığında kayıt içindeki baytı bulmak için sayım önde gelen sıfırları kullanır ve sayfa geçişini kontrol ettikten sonra AArch64'ün verimli hizalanmamış erişiminden yararlanır.

Ayrıca: Optimizasyon etkinken bu kod 6,5x neden daha yavaş? strlenbüyük bir arabellek ve gcc için satır içi bilmek iyi olabilir basit bir asm uygulaması ile x86 asm hızlı ve yavaş neyin hakkında biraz daha ayrıntı var . (Bazı gcc sürümleri rep scasbakıllıca sıralıdır, ki bu çok yavaştır veya bir seferde 4 baytlık bir bithack. Bu nedenle GCC'nin satır içi stlen tarifi güncellenmesi veya devre dışı bırakılması gerekir.)

Asm'da C stili "tanımlanmamış davranış" yoktur ; bellekteki baytlara istediğiniz gibi erişmek güvenlidir ve geçerli baytları içeren hizalanmış bir yük hata veremez. Bellek koruması hizalanmış sayfa ayrıntı düzeyi ile gerçekleşir; hizalanmış erişim, sayfa sınırını geçemediğinden daha dar. X86 ve x64'te aynı sayfadaki bir arabellek sonuna kadar okumak güvenli midir? Aynı mantık, bu C saldırısının derleyicilerin bu işlevin bağımsız bir satır içi uygulaması için oluşturmalarını sağladığı makine kodu için de geçerlidir.

Derleyici, bilinmeyen satır içi olmayan bir işlevi çağırmak için kod yayınladığında, işlevin herhangi bir genel değişkeni / tüm değişkenleri ve işaretçisi olabilecek herhangi bir belleği değiştirdiğini varsaymalıdır. yani adres çıkışları olmayan yerliler hariç her şey çağrı boyunca bellekte senkronize olmalıdır. Bu açıkça asm ile yazılmış fonksiyonlar için olduğu kadar kütüphane fonksiyonları için de geçerlidir. Bağlantı zamanı optimizasyonunu etkinleştirmezseniz, ayrı çeviri birimleri (kaynak dosyaları) için bile geçerlidir.


Bu neden glibc'nin bir parçası olarak güvenli ama başka türlü değil .

En önemli faktör, bunun strlenbaşka hiçbir şeye satır içi olamamasıdır. Bunun için güvenli değil; katı takma UB içerir ( charan ile veri okuma unsigned long*). char*Başka takma şey izin verilir ancak tersi olduğunu değil gerçek .

Bu, önceden derlenmiş bir kitaplık (glibc) için bir kitaplık işlevidir. Arayanlara bağlantı zamanı optimizasyonu ile satır içine alınmaz. Bu, tek başına bir sürümü için güvenli makine kodunu derlemek zorunda olduğu anlamına gelir strlen. Taşınabilir / güvenli C olması gerekmez.

GNU C kütüphanesi sadece GCC ile derlenmelidir. Görünüşe göre GNU uzantılarını destekleseler bile clang veya ICC ile derlemek desteklenmiyor . GCC, bir C kaynak dosyasını makine kodunun bir nesne dosyasına dönüştüren vaktinden önce derleyicilerdir. Bir tercüman değil, bu yüzden derleme zamanında satır içi gelmedikçe, hafızadaki baytlar sadece hafızadaki baytlardır. başka bir deyişle, farklı türlerdeki erişim, birbirinin içine girmeyen farklı işlevlerde gerçekleştiğinde katı kenar yumuşatma UB tehlikeli değildir.

strlenDavranışlarının ISO C standardı tarafından tanımlandığını unutmayın . Bu işlev adı özellikle uygulamanın bir parçasıdır . GCC gibi derleyiciler -fno-builtin-strlen, siz kullanmadıkça adı yerleşik bir işlev olarak görürler , bu nedenle strlen("foo")derleme zamanı sabiti olabilir 3. Kütüphanedeki tanım sadece gcc kendi tarifini ya da başka bir şeyi satırlamak yerine ona bir çağrı yapmaya karar verdiğinde kullanılır.

Derleme zamanında UB derleyici tarafından görülmezse , aklı başında makine kodu alırsınız. Makine kod no-UB durum için işe vardır ve hatta eğer istediği için, asm türleri arayan sivri-bellek veri koymak için kullanılan algılamak için bir yolu yoktur.

Glibc, bağlantı zamanı optimizasyonu ile satır içine alınamayan bağımsız bir statik veya dinamik kitaplığa derlenmiştir. glibc'nin yapı komut dosyaları, bir programa satır içi bağlantı kurarken bağlantı zamanı optimizasyonu için makine kodu + gcc içeren GIMPLE dahili temsilini "yağ" statik kitaplıkları oluşturmaz. (yani , ana programa bağlantı zamanı optimizasyonuna libc.akatılmayacaktır -flto.) glibc'yi bu şekilde kullanmak, bunu gerçekten kullanan hedeflerde.c potansiyel olarak güvenli olmayacaktır .

Aslında @zwol'un yorumlarına göre, glibc kaynak dosyaları arasında satırlama yapılması durumunda kırılabilecek "kırılgan" kod nedeniyle LTO, glibc'in kendisini oluştururken kullanılamaz . (Bazı dahili kullanımlar vardır strlen, örneğin printfuygulamanın bir parçası olarak )


Bu strlenbazı varsayımlar yapar:

  • CHAR_BIT, 8'in katıdır . Tüm GNU sistemlerinde doğrudur. POSIX 2001 bile garanti veriyor CHAR_BIT == 8. (Bu CHAR_BIT= 16veya 32bazı DSP'lere sahip sistemler için güvenli görünüyor ; hizalanmamış prolog döngüsü sizeof(long) = sizeof(char) = 1her işaretçi her zaman hizalanmış ve p & sizeof(long)-1her zaman sıfır olduğu için her zaman 0 yineleme çalıştırır .) Ancak karakterlerin 9 olduğu ASCII olmayan bir karakter kümeniz varsa veya 12 bit genişliğinde, 0x8080...yanlış desen.
  • (belki) unsigned long4 veya 8 bayttır. Ya da belki unsigned long8'e kadar herhangi bir boyut için işe yarayabilir ve bunu assert()kontrol etmek için bir kullanır .

Bu ikisi mümkün UB değil, sadece bazı C uygulamalarına taşınabilir değiller. Bu kod, çalıştığı platformlarda C uygulamasının bir parçasıdır (veya öyleydi) , bu yüzden iyi.

Bir sonraki varsayım potansiyel C UB'dir:

  • Geçerli bayt içeren hizalanmış bir yük hata veremez ve gerçekte istediğiniz nesnenin dışındaki baytları yok saydığınız sürece güvenlidir. (Bellek koruması hizalanmış sayfalık boyu ile her GNU sistemlerinde asm ve tüm normal CPU'lar üzerinde Doğru olur çünkü. O? X86 ve x64 aynı sayfa içinde bir tampon sonu okunmaya güvenli mi UB C güvenli Derleme zamanında görünmez. Inlining olmadan, buradaki durum budur. Derleyici 0, ilkinden önce okumanın UB olduğunu kanıtlayamaz ; örneğin char[]içeren bir C dizisi olabilir {1,2,0,3})

Bu son nokta, burada bir C nesnesinin sonunu okumayı güvenli kılan şeydir. Mevcut derleyicilerle satır içi oluştururken bile oldukça güvenlidir, çünkü şu anda bir yürütme yolunu ima etmenin ulaşılamaz olduğunu düşünmüyorlar. Ama yine de, bu satır içi izin verirseniz, sıkı takma zaten bir göstericidir.

Sonra, Linux çekirdeğinin işaretçi dökümünü kullanan eski güvensiz memcpy CPP makrosu gibi sorunlarınız olurdu unsigned long( gcc, katı takma ve korku hikayeleri ).

Bu strlen, genel olarak böyle şeylerden kurtulabileceğiniz döneme kadar uzanır ; GCC3'ten önce "sadece inlining olmadığında" uyarı olmadan oldukça güvenli olurdu.


Yalnızca çağrı / ret sınırlarına baktığımızda görünen UB bize zarar veremez. (örneğin, bir bu arama char buf[]bir dizi üzerinde yerine unsigned long[]bir döküntünün const char*). Makine kodu taş haline getirildiğinde, sadece bellekteki baytlarla ilgilenir. Satır içi olmayan bir işlev çağrısı, arayanın herhangi bir / tüm belleği okuduğunu varsaymalıdır.


Sıkı takma UB olmadan bunu güvenle yazma

GCC türü özelliğimay_alias , A tipi ile aynı ad-şey tedaviyi verir char*. (@KonradBorowsk tarafından önerilmiştir). GCC başlıkları şu anda bunu __m128iher zaman güvenle yapabilmeniz için x86 SIMD vektör türleri için kullanıyor _mm_loadu_si128( (__m128i*)foo ). (Bkz Is `donanım vektör pointer ve ilgili tip tanımlanmamış bir davranış? Arasına reinterpret_cast`ing yapar ve ortalama ne değildir hakkında daha fazla ayrıntı için.)

strlen(const char *char_ptr)
{
  typedef unsigned long __attribute__((may_alias)) aliasing_ulong;

  aliasing_ulong *longword_ptr = (aliasing_ulong *)char_ptr;
  for (;;) {
     unsigned long ulong = *longword_ptr++;  // can safely alias anything
     ...
  }
}

aligned(1)İle bir türü ifade etmek için de kullanabilirsiniz alignof(T) = 1.
typedef unsigned long __attribute__((may_alias, aligned(1))) unaligned_aliasing_ulong;

memcpyModern derleyicilerin tek bir yük talimatı olarak nasıl sıralanacağını bildikleri ISO'da bir takma yükü ifade etmenin taşınabilir bir yolu . Örneğin

   unsigned long longword;
   memcpy(&longword, char_ptr, sizeof(longword));
   char_ptr += sizeof(longword);

Bu aynı zamanda hizalanmamış yükler için çalıştığı için memcpyeserler olarak -eğer tarafından char-at bir yokmuş erişimi. Ancak pratikte modern derleyiciler memcpyçok iyi anlıyor .

Buradaki tehlike GCC değilse olduğunu biliyoruz , bu kesin char_ptrkelime hizalanmış olup, bu asm hizalanmamış yükleri desteklemiyor olabilir bazı platformlarda zaten iç olmayacaktır. örneğin MIPS64r6'dan önceki MIPS veya daha eski ARM. Gerçek bir fonksiyonunuz varsa, memcpysadece bir kelime yüklemek (ve onu başka bir bellekte bırakmak) için çağrıda bulunursanız , bu bir felaket olacaktır. GCC bazen kod bir işaretçiyi ne zaman hizaladığını görebilir. Veya uzun bir sınıra ulaşan bir seferlik char döngüsünün ardından
p = __builtin_assume_aligned(p, sizeof(unsigned long));

Bu, nesnenin okunmasını mümkün UB'den kaçınmaz, ancak mevcut GCC ile pratikte tehlikeli değildir.


Neden el ile optimize edilmiş C kaynağı gereklidir: mevcut derleyiciler yeterince iyi değil

Elle optimize edilmiş asm, yaygın olarak kullanılan standart kütüphane işlevi için her son performans düşüşünü istediğinizde daha da iyi olabilir. Özellikle gibi bir şey için memcpy, ama aynı zamanda strlen. Bu durumda SSE2'den yararlanmak için C'yi x86 intrinsics ile kullanmak daha kolay olmazdı.

Ancak burada, herhangi bir ISA'ya özgü özellik olmadan saf ve bithack C sürümünden bahsediyoruz.

(Bence strlenmümkün olduğunca hızlı çalışmasını sağlayacak kadar yaygın olarak kullanılan bir verilen olarak alabiliriz . Bu yüzden soru, daha basit kaynaktan verimli makine kodu alıp alamayacağımızdır. Hayır, yapamayız.)

Mevcut GCC ve clang, yineleme sayısının ilk yinelemeden önce bilinmediği döngüleri otomatik olarak vektörleştiremez . (örneğin , ilk yinelemeyi çalıştırmadan önce döngünün en az 16 yineleme çalıştırıp çalıştırmayacağını kontrol etmek gerekir .) örn. derleyiciler.

Bu, arama döngülerini veya veriye if()breakve sayaca sahip başka bir döngüyü içerir .

ICC (Intel'in x86 için derleyicisi) bazı arama döngülerini otomatik olarak vektörleştirebilir, ancak yine strlende OpenBSD'nin libc kullanımları gibi basit / naif bir C için sadece bir seferde bayt bayramı yapar . ( Godbolt ). ( @ Peske'nin cevabından ).

strlenMevcut derleyicilerle performans için elle optimize edilmiş bir libc gereklidir . Bir seferde 1 bayta gitmek (geniş süperskalar CPU'larda döngü başına belki 2 bayt açma) ana bellek, döngü başına yaklaşık 8 bayta kadar ulaşabiliyorsa ve L1d önbellek döngü başına 16 ila 64 verebiliyorsa acınasıdır. (Haswell ve Ryzen'den beri modern ana x86 CPU'larda döngü başına 2x 32 bayt yük. Sadece 512 bit vektörleri kullanmak için saat hızlarını azaltabilen AVX512 sayılmıyor; bu nedenle glibc muhtemelen bir AVX512 sürümü eklemek için acele etmiyor Her ne kadar 256 bit vektörlerle, AVX512VL + BW maskeli bir maske ile karşılaştırılır ve ktestveya uops / yinelemesini azaltarak daha hiper iş parçacığı kortesthaline getirebilir strlen.)

Buraya x86 olmayanları dahil ediyorum, bu "16 bayt". Örneğin, çoğu AArch64 işlemcisi en azından bunu yapabilir ve bence kesinlikle daha fazlasını yapabilir. Ve bazılarının strlenbu yük bant genişliğine ayak uyduracak kadar yürütme kapasitesi vardır .

Tabii ki büyük dizelerle çalışan programlar, örtük uzunluktaki C dizelerinin uzunluğunu çok sık bulmak zorunda kalmamak için genellikle uzunlukları takip etmelidir. Ancak kısa ve orta uzunlukta performans hala elle yazılmış uygulamalardan yararlanır ve eminim bazı programlar orta uzunlukta dizelerde strlen kullanmaktadır.


12
Birkaç not: (1) Şu anda glibc'in kendisini GCC dışında herhangi bir derleyici ile derlemek mümkün değildir. (2) Glibc'in kendisini bağlantı zamanı optimizasyonları etkinken derlemek mümkün değildir, çünkü tam olarak bu tür durumlar vardır, çünkü satırlayıcının satır içi olmasına izin verilirse UB'yi görür. (3) CHAR_BIT == 8bir POSIX gereksinimidir (-2001 rev. İtibariyle; buraya bakın ). (4) C yedek uygulaması strlenbazı desteklenen CPU'lar için kullanılır, en yaygın olanının MIPS olduğuna inanıyorum.
zwol

1
İlginç bir şekilde, sıkı örtüşme UB, __attribute__((__may_alias__))öznitelik kullanılarak düzeltilebilir (bu taşınabilir değildir, ancak glibc için iyi olmalıdır).
Konrad Borowski

1
@SebastianRedl: a yoluyla herhangi bir nesneyi okuyabilir / yazabilirsiniz char*, ancak a üzerinden bir char nesneyi (örneğin a'nın bir parçası char[]) okumak / yazmak hala UB'dir long*. Sıkı takma ad kuralı ve 'char *' işaretçileri
Peter Cordes

1
C ve C ++ standartları CHAR_BITen az 8 ( C11'in qv Ek E) olması gerektiğini söylüyor , bu yüzden en az 7 bit charbir dil avukatının endişelenmesi gereken bir şey değil. Bu, “UTF − 8 dize değişmezleri için dizi öğelerinin türü charvardır ve UTF − 8'de kodlandığı gibi çok baytlı karakter dizisinin karakterleriyle başlatılır.
Davislor

2
Bu analiz, harika bir cevap vermekten başka, kodu şu anda devre dışı bırakılmış optimizasyonlar karşısında daha sağlam hale getiren bir yama önermek için iyi bir temel gibi görünüyor.
Tekilleştirici

61

Bağladığınız dosyadaki yorumlarda açıklanmıştır:

 27 /* Return the length of the null-terminated string STR.  Scan for
 28    the null terminator quickly by testing four bytes at a time.  */

ve:

 73   /* Instead of the traditional loop which tests each character,
 74      we will test a longword at a time.  The tricky part is testing
 75      if *any of the four* bytes in the longword in question are zero.  */

C'de, verimlilik hakkında ayrıntılı bir şekilde muhakeme yapmak mümkündür.

Bir boş karakter arayan tek tek karakterler arasında yineleme yapmak, bu kodun yaptığı gibi, aynı anda birden fazla bayt test etmekten daha az etkilidir.

Ek karmaşıklık, test edilen dizginin bir kerede birden fazla bayt test etmeye başlamak için doğru yorumda hizalanmasını sağlamaktan (yorumlarda açıklandığı gibi bir uzun kelime sınırı boyunca) ve varsayımların kod kullanıldığında veri türlerinin boyutları hakkında ihlal edilmez.

In çoğu (hepsi değil) Modern yazılım geliştirme, verimlilik bu özen gerekli veya fazladan kod karmaşıklık maliyeti değmez değil.

Bunun gibi verimliliğe dikkat etmenin mantıklı olduğu bir yer, bağladığınız örnek gibi standart kütüphanelerdedir.


Kelime sınırları hakkında daha fazla bilgi edinmek istiyorsanız, bu soruya ve bu mükemmel wikipedia sayfasına bakın


39

Burada büyük cevaplara ek olarak, soruda bağlantılı kodun GNU'nun uygulanması için olduğunu belirtmek istiyorum strlen.

Arasında OpenBSD uygulanmasıstrlen söz konusu teklif edilen koduna çok benzer. Bir uygulamanın karmaşıklığı yazar tarafından belirlenir.

...
#include <string.h>

size_t
strlen(const char *str)
{
    const char *s;

    for (s = str; *s; ++s)
        ;
    return (s - str);
}

DEF_STRONG(strlen);

EDIT : Yukarıda bağladığım OpenBSD kodu, kendi asm uygulaması olmayan ISA'lar için bir yedek uygulama gibi görünüyor. strlenMimarlığa bağlı olarak farklı uygulamalar vardır . Örneğin, amd64strlen kodu asm'dir. PeterCordes yorumlarına benzer / cevap olmayan çare GNU uygulamaları yanı asm olduğuna işaret.


5
Bu, OpenBSD ve GNU araçlarında optimize edilen farklı değerlerin çok güzel bir örneğidir.
Jason

11
Bu glibc'in taşınabilir yedek uygulamasıdır. Tüm büyük ISA'lar, glibc'de, yardımcı olduğunda SIMD kullanarak elle yazılmış asm uygulamalarına sahiptir (örn. X86'da). Bkz. Code.woboq.org/userspace/glibc/sysdeps/x86_64/multiarch/… ve code.woboq.org/userspace/glibc/sysdeps/aarch64/multiarch/…
Peter Cordes

4
OpenBSD versiyonunda bile orijinalin önlediği bir kusur var! s - strSonuç temsil edilemiyorsa, davranışı tanımsızdır ptrdiff_t.
Antti Haapala

1
@AnttiHaapala: GNU C'de maksimum nesne boyutu PTRDIFF_MAX. Ancak yine mmapde Linux'takinden daha fazla bellek elde etmek mümkündür (örneğin, x86-64 çekirdeği altındaki 32 bitlik bir işlemde, hata almaya başlamadan önce yaklaşık 2,7 GB'lık bitişik eşleştirebilirim). OpenBSD hakkında IDK; çekirdek return, boyut içinde segfaulting veya durmadan buna ulaşmayı imkansız hale getirebilir . Ama evet, teorik C UB'yi önleyen savunma kodlamasının OpenBSD'nin yapmak isteyeceği bir şey olacağını düşünürdünüz. Hatta strleninline olamaz ve gerçek derleyiciler sadece bir çıkarma için derlemek olacaktır.
Peter Cordes

2
@PeterCordes tam olarak. OpenBSD'de aynı şey, örneğin i386 meclisi: cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libc/arch/i386/string/…
dchest

34

Kısacası, bu standart kütüphanenin hangi derleyicinin derlendiğini bilerek yapabileceği bir performans optimizasyonudur - standart bir kütüphane yazmadıkça ve belirli bir derleyiciye bağlı olamadığınız sürece böyle bir kod yazmamalısınız. Özellikle, aynı anda bayt hizalama sayısını işlemektedir - 32 bit platformlarda 4, 64 bit platformlarda 8. Bu, saf bayt yinelemesinden 4 veya 8 kat daha hızlı olabileceği anlamına gelir.

Bunun nasıl çalıştığını açıklamak için aşağıdaki resmi göz önünde bulundurun. 32 bit platformun burada olduğunu varsayın (4 bayt hizalama).

Diyelim ki, "Merhaba, dünya!" dize bir argüman olarak sağlandı strlen. CPU, bellekte hizalanmış şeyleri sevdiğinden (ideal olarak address % sizeof(size_t) == 0), hizalamadan önceki baytlar, yavaş yöntem kullanılarak bayt-bayt olarak işlenir.

Daha sonra, hizalama boyutundaki her yığın için, (longbits - 0x01010101) & 0x80808080 != 0bir tamsayı içindeki baytlardan herhangi birinin sıfır olup olmadığını kontrol eder. Bu hesaplama, baytlardan en az biri daha yüksek olduğunda yanlış pozitiftir 0x80, ancak daha sık çalışmaması gerekir. Durum böyle değilse (sarı alanda olduğu gibi) uzunluk hizalama boyutuna göre artırılır.

Bir tamsayı içindeki baytlardan herhangi biri sıfır (veya 0x81) olursa, sıfırın konumunu belirlemek için dize bayt-bayt kontrol edilir.

Bu sınır dışı erişim sağlayabilir, ancak bir hizalama içinde olduğundan, iyi olmamak daha olasıdır, bellek haritalama birimleri genellikle bayt düzeyinde hassasiyete sahip değildir.


Bu uygulama glibc'nin bir parçasıdır. GNU sistemi, sayfa ayrıntı düzeyi ile bellek koruması yapar. Bu nedenle evet, geçerli bayt içeren hizalanmış bir yük güvenlidir.
Peter Cordes

size_thizalandığı garanti edilmez.
SS Anne

32

Kodun doğru, bakımı ve hızlı olmasını istiyorsunuz. Bu faktörlerin farklı önemi vardır:

"doğru" kesinlikle şarttır.

"sürdürülebilir", kodu ne kadar koruyacağınıza bağlıdır: strlen, 40 yılı aşkın bir süredir Standart C kitaplığı işlevidir. Değişmeyecek. Bu nedenle, bu işlev için sürdürülebilirlik oldukça önemsizdir.

"Hızlı": Birçok uygulamada, strcpy, strlen vb. Önemli miktarda yürütme süresi kullanır. Bu karmaşık ancak aynı zamanda çok karmaşık değil, aynı zamanda hızlı bir şekilde derleyiciyi geliştirerek strlen uygulaması kahramanca çaba gerektirecektir.

Hızlı olmanın başka bir avantajı daha vardır: Programcılar "strlen" çağrısının bir dizgideki bayt sayısını ölçebilecekleri en hızlı yöntem olduğunu öğrendiklerinde, işleri daha hızlı hale getirmek için artık kendi kodlarını yazmaya cazip gelmezler.

Dolayısıyla, stlen için hız, yazacağınız çoğu koddan çok daha önemlidir ve sürdürülebilirlik çok daha az önemlidir.

Neden bu kadar karmaşık olmalı? 1.000 baytlık bir dizeniz olduğunu varsayalım. Basit uygulama 1.000 baytı inceleyecektir. Mevcut bir uygulama muhtemelen bir seferde 64 bit kelimeleri inceleyecektir, bu da 125 64 bit veya sekiz baytlık kelimeler anlamına gelir. Hatta her seferinde 32 byte'ı inceleyen vektör talimatlarını bile kullanabilir, bu da daha karmaşık ve daha hızlı olabilir. Vektör talimatlarını kullanmak, biraz daha karmaşık ama oldukça basit bir koda yol açar, 64 bitlik bir kelimedeki sekiz bayttan birinin sıfır olup olmadığını kontrol etmek için bazı akıllı numaralar gerekir. Dolayısıyla orta ve uzun dizeler için bu kodun yaklaşık dört kat daha hızlı olması beklenebilir. Strlen kadar önemli bir işlev için, daha karmaşık bir işlev yazmaya değer.

PS. Kod çok taşınabilir değil. Ancak, uygulamanın bir parçası olan Standart C kütüphanesinin bir parçasıdır - taşınabilir olması gerekmez.

PPS. Birisi, bir hata ayıklama aracının bir dizenin sonundaki baytlara erişmekten şikayet ettiği bir örnek yayınladı. Aşağıdakileri garanti eden bir uygulama tasarlanabilir: p, bir bayt için geçerli bir işaretçi ise, aynı hizalı blokta C standardına göre tanımsız davranış olacak bir bayta erişim, belirtilmemiş bir değer döndürür.

Bitki koruma ürünlerinin. Intel, sonraki işlemcilerine strstr () işlevi için bir yapı taşı oluşturan yönergeler ekledi (bir dizede bir alt dize bulma). Açıklamaları akıl karıştırıcıdır, ancak bu işlevi muhtemelen 100 kat daha hızlı hale getirebilirler. (Temel olarak, "Hello, world!" İçeren bir dizi ve 16 bayt "HelloHelloHelloH" ile başlayan ve daha fazla bayt içeren bir dizi göz önüne alındığında, a dizesinin b dizininde 15'ten daha erken başlamadığını fark eder) .


Ya da ... Çok sayıda dize tabanlı işleme yaptığımı ve bir darboğaz olduğunu fark edersem, muhtemelen strlen geliştirmek yerine Pascal Strings kendi sürümünü uygulayacağım ...
Baldrickk

1
Kimse sizden steni geliştirmenizi istemiyor . Ancak bunu yeterince iyi yapmak, insanların kendi iplerini uyguladıkları gibi saçmalıklardan kaçınır.
gnasher729


24

Kısaca: bir dizgi bayt baytını kontrol etmek, bir seferde daha fazla miktarda veri alabilen mimarilerde potansiyel olarak yavaş olacaktır.

Boş sonlandırma kontrolü 32 veya 64 bit temelinde yapılabiliyorsa, derleyicinin yapması gereken kontrol miktarını azaltır. Bağlantılı kod, belirli bir sistemi göz önünde bulundurarak yapmaya çalışır. Adresleme, hizalama, önbellek kullanımı, standart olmayan derleyici kurulumları vb. Hakkında varsayımlar yaparlar.

Örneğinizde olduğu gibi bayt bayt okumak, 8 bit CPU'da veya standart C'de yazılmış taşınabilir bir lib yazarken mantıklı bir yaklaşım olacaktır.

Hızlı / iyi kodun nasıl yazılacağını önermek için C standart kütüphanelerine bakmak iyi bir fikir değildir, çünkü taşınabilir değildir ve standart olmayan varsayımlara veya kötü tanımlanmış davranışlara dayanacaktır. Yeni başlayan biriyseniz, bu kodu okumak muhtemelen eğitimden daha zararlı olacaktır.


1
Tabii ki optimize edicinin bu döngüyü açma veya otomatik vektör etme olasılığı yüksektir ve ön getirici bu erişim düzenini önemsiz bir şekilde algılayabilir. Bu hilelerin gerçekten modern işlemciler için önemli olup olmadığı test edilmelidir. Eğer kazanılacak bir kazanç varsa, muhtemelen vektör talimatları kullanıyor demektir.
russbishop

6
@russbishop: Umarım öyle olur, ama hayır. GCC ve clang, yineleme sayısının ilk yinelemeden önce bilinmediği otomatik-döngüler döngülerine tamamen sahip değildir. Bu, arama döngülerini veya verilere bağlı başka bir döngüyü içerir if()break. ICC bu tür döngüleri otomatik olarak vektörleştirebilir, ancak IDK saf bir strlen ile ne kadar iyi olduğunu gösterir. Ve evet, SSE2 pcmpeqb/ pmovmskbolduğunu çok bir seferde 16 bayt test strlen için iyi. code.woboq.org/userspace/glibc/sysdeps/x86_64/strlen.S.html glibc'nin SSE2 sürümüdür. Ayrıca bkz Bu Q ve A .
Peter Cordes

Oof, bu talihsiz. Ben genellikle çok anti-UB değilim ama C dizeleri belirtmek gibi teknik olarak UB arabellek sonu vektörleştirme bile izin okumak gerekir. Aynı şey hizalama gerektirdiğinden ARM64 için de geçerli.
russbishop

-6

Diğer cevaplarda belirtilmeyen önemli bir şey, FSF'nin tescilli kodun GNU projelerine girmemesini sağlamak konusunda çok dikkatli olmasıdır. Gelen Standartları Kodlama GNU altında tescilli Programları değinen , mevcut tescilli kod ile karıştırılmamalıdır edemeyeceği bir şekilde uygulanmasını düzenleme hakkında bir uyarı vardır:

GNU üzerinde çalışırken ya da çalışırken hiçbir koşulda Unix kaynak koduna başvurmayın! (Veya diğer herhangi bir özel programa.)

Bir Unix programının iç kısımlarına dair belirsiz bir hatırlamanız varsa, bu kesinlikle bir taklit yazamayacağınız anlamına gelmez, ancak taklidi dahili olarak farklı satırlarda düzenlemeye çalışın, çünkü bu muhtemelen sonuçlarla alakasız ve farklı Unix sürümü.

Örneğin, Unix yardımcı programları genellikle bellek kullanımını en aza indirecek şekilde optimize edilmiştir; bunun yerine hız için giderseniz , programınız çok farklı olacaktır.

(Vurgu benim.)


5
Bu soruya nasıl cevap veriyor?
SS Anne

1
OP'deki soru "bu daha basit kod daha iyi çalışmaz mı?" İdi ve bu her zaman teknik değer konusunda karar verilmemiş bir sorudur. GNU gibi bir proje için, yasal tuzaklardan kaçınmak, "daha iyi çalışmak" kodunun önemli bir parçasıdır ve "bariz" uygulamalarının strlen()mevcut koda benzer veya aynı olması muhtemeldir. Glibc'in uygulaması gibi "çılgın" bir şey bu şekilde izlenemez. Ne kadar yasal wrangling göz önüne alındığında rangeCheck- 11 kod satırı üzerinde! - Google / Oracle mücadelesinde, FSF'nin endişesinin iyi olduğunu söyleyebilirim.
Jack Kelly
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.