在C ++程序中以编程方式检测字节序


211

有没有一种程序化的方法来检测您是采用大端还是小端架构?我需要能够编写将在Intel或PPC系统上执行的代码,并使用完全相同的代码(即无条件编译)。


4
为了完整起见,这里是别人的关于设法计字节序问题的链接(在编译时):stackoverflow.com/questions/280162/...
费萨尔瓦利

14
为什么不在编译时确定字节序?它可能无法在运行时更改。
短暂

3
AFAIK,没有可靠且通用的方法可以做到这一点。gcc.gnu.org/ml/gcc-help/2007-07/msg00342.html
user48956

Answers:


174

我不喜欢基于punning类型的方法-编译器经常会警告它。这正是工会的宗旨!

bool is_big_endian(void)
{
    union {
        uint32_t i;
        char c[4];
    } bint = {0x01020304};

    return bint.c[0] == 1; 
}

该原理与其他人所建议的类型情况相同,但这更加清楚-根据C99,可以保证它是正确的。与直接指针强制转换相比,gcc更喜欢这种方式。

这也比在编译时固定字节序要好得多-对于支持多体系结构的OS(例如,Mac os x上的胖二进制文件),这对ppc / i386都适用,否则很容易搞乱其他情况。


51
我不建议将变量命名为“ bint” :)
mkb

42
您确定定义正确吗?在C ++中,一次只能激活联合的一个成员-即,您不能使用一个成员名称进行分配,而可以使用另一个成员名称进行读取(尽管布局兼容结构存在例外)
Faisal Vali

26
@Matt:我调查了Google,bint似乎有一个我不知道的英文意思:)
David Cournapeau 09年

17
我已经测试过了,在gcc 4.0.1和gcc 4.4.1中都可以在编译时确定此函数的结果并将其视为常量。这意味着,如果分支仅依赖于此函数的结果,并且永远不会在所涉及的平台上使用,则编译器将删除。对于htonl的许多实现,情况可能并非如此。
2009年

6
这个解决方案真的可移植吗?如果CHAR_BIT != 8呢?
zorgit 2015年

80

您可以通过设置int并掩盖位来完成此操作,但是最简单的方法可能只是使用内置的网络字节转换操作(因为网络字节顺序始终是大字节序)。

if ( htonl(47) == 47 ) {
  // Big endian
} else {
  // Little endian.
}

位修改可能会更快,但是这种方式简单,直接并且几乎不可能搞乱。


1
网络转换操作还可用于将所有内容转换为大字节序,从而解决Jay可能遇到的其他问题。
布赖恩2009年

6
@sharptooth-慢是一个相对的术语,但是是的,如果速度确实是一个问题,请在程序开始时使用它一次,并以endianness设置全局变量。
Eric Petroelje 09年

5
htonl还有另一个问题:在某些平台(Windows?)上,它并不驻留在C运行时库中,而是驻留在与网络相关的其他库(套接字等)中。如果您不需要该库,那么这对于仅一个功能来说是一个很大的障碍。
David Cournapeau 09年

7
请注意,在Linux(gcc)上,htonl在编译时会不断折叠,因此这种形式的表达式根本没有运行时开销(即,将它恒定折叠为1或0,然后通过消除死代码来删除if的其他分支)
bdonlan

2
另外,在x86上,可以使用内联汇编器非常有效地实现htonl(在Linux / gcc上),尤其是如果您针对支持该BSWAP操作的微体系结构,则尤其如此。
bdonlan

61

请看这篇文章

这是一些代码来确定您的机器类型是什么

int num = 1;
if(*(char *)&num == 1)
{
    printf("\nLittle-Endian\n");
}
else
{
    printf("Big-Endian\n");
}

25
请记住,这取决于int和char是不同的长度,几乎总是这样,但不能保证。
David Thornley,2009年

10
我曾在short int和char大小相同的嵌入式系统上工作...我不记得常规int还是该大小(2字节)。
rmeador

2
为什么这个答案几乎是唯一的答案,而这并没有让我认为“伙计,你在做什么?”,这是大多数答案的情况:o
hanshenrik

2
@Shillard int至少必须这么大,但标准中并没有要求将char限制为更少!如果您看一下TI F280x系列,您会发现CHAR_BIT为16且sizeof(int)== sizeof(char),而您提到的限制保持绝对良好...
阿空加瓜

5
为什么不使用uint8_t和uint16_t?
罗德里戈

58

如果可以std::endian访问C ++ 20编译器(例如GCC 8+或Clang 7+),则可以使用。

注:std::endian开始<type_traits>,但被转移<bit>在2019科隆会议。GCC 8,Clang 7、8和9包含在其中,<type_traits>而GCC 9+和Clang 10+包含在其中<bit>

#include <bit>

if constexpr (std::endian::native == std::endian::big)
{
    // Big endian system
}
else if constexpr (std::endian::native == std::endian::little)
{
    // Little endian system
}
else
{
    // Something else
}

5
作为所有人,我都可以使用C ++ 17和20个草稿/建议,但是到目前为止,是否存在任何C ++ 20编译器?
Xeverous

@Xeverous它仅需要范围内的枚举,因此我怀疑大多数供应商会将其添加到其stdlib实现中,作为其早期更改之一。
法拉普

@Xeverous GCC 8已发布并支持它。
Lyberta '18年

在该问题的30多个答案中,这似乎是唯一一个完全准确的答案(另一个答案至少在一定程度上是正确的)。
IInspectable

40

通常在编译时(特别是出于性能原因)使用编译器提供的头文件或创建自己的头文件来完成此操作。在Linux上,您具有头文件“ /usr/include/endian.h”


8
我不敢相信这还没有得到更高的投票。这并不意味着字节序会在已编译的程序下发生变化,因此永远不需要运行时测试。
Dolda2000

@ Dolda2000它可能会看到ARM字节序模式。
Tyzoid

10
@Tyzoid:不,即使处理器能够执行编译程序,编译程序也将始终以其编译时的字节序模式运行。
Dolda2000 '16

16

令我惊讶的是,没有人提到预处理器默认定义的宏。尽管这些将取决于您的平台;它们比编写自己的字节序检查要干净得多。

例如; 如果我们看一下GCC定义的内置宏(在X86-64机器上):

:| gcc -dM -E -x c - |grep -i endian
#define __LITTLE_ENDIAN__ 1

在PPC机器上,我得到:

:| gcc -dM -E -x c - |grep -i endian
#define __BIG_ENDIAN__ 1
#define _BIG_ENDIAN 1

:| gcc -dM -E -x c -魔术会打印出所有内置宏)。


7
这些宏根本不会一致显示。例如,在Redhat 6存储库中的gcc 4.4.5中,运行echo "\n" | gcc -x c -E -dM - |& grep -i 'endian'不会返回任何内容,而/usr/sfw/binSolaris中的gcc 3.4.3(无论如何)都具有以下定义。我在VxWorks Tornado(gcc 2.95)-vs- VxWorks Workbench(gcc 3.4.4)上看到了类似的问题。
布莱恩·范登堡

15

嗯……令我惊讶的是,没有人意识到编译器会简单地优化测试,并将固定的结果作为返回值。这使得上面的所有代码示例实际上无效。唯一会返回的是编译时的字节序!是的,我测试了所有上述示例。这是MSVC 9.0(Visual Studio 2008)的示例。

纯C代码

int32 DNA_GetEndianness(void)
{
    union 
    {
        uint8  c[4];
        uint32 i;
    } u;

    u.i = 0x01020304;

    if (0x04 == u.c[0])
        return DNA_ENDIAN_LITTLE;
    else if (0x01 == u.c[0])
        return DNA_ENDIAN_BIG;
    else
        return DNA_ENDIAN_UNKNOWN;
}

拆卸

PUBLIC  _DNA_GetEndianness
; Function compile flags: /Ogtpy
; File c:\development\dna\source\libraries\dna\endian.c
;   COMDAT _DNA_GetEndianness
_TEXT   SEGMENT
_DNA_GetEndianness PROC                 ; COMDAT

; 11   :     union 
; 12   :     {
; 13   :         uint8  c[4];
; 14   :         uint32 i;
; 15   :     } u;
; 16   : 
; 17   :     u.i = 1;
; 18   : 
; 19   :     if (1 == u.c[0])
; 20   :         return DNA_ENDIAN_LITTLE;

    mov eax, 1

; 21   :     else if (1 == u.c[3])
; 22   :         return DNA_ENDIAN_BIG;
; 23   :     else
; 24   :        return DNA_ENDIAN_UNKNOWN;
; 25   : }

    ret
_DNA_GetEndianness ENDP
END

也许可以仅为此功能关闭任何编译时优化,但是我不知道。否则,尽管它不是可移植的,但有可能在组装时对其进行硬编码。即使那样,它也可能被优化。这让我觉得我需要一些笨拙的汇编器,为所有现有的CPU /指令集实现相同的代码,好吧....没关系。

另外,这里有人说,字节序在运行时不会改变。错误。那里有bi-endian机器。它们的字节顺序可以在执行期间变化。另外,不仅有Little Endian和Big Endian,还有其他endianness(单词)。

我讨厌同时喜欢编码...


11
您是否不必重新编译以在其他平台上运行?
bobobobo

2
尽管它适用于MSVC,但并非在所有情况下都适用于所有GCC版本。因此,关键循环内的“运行时检查”可能在编译时正确解开。没有100%的保证。
青色

21
没有像大端x86处理器那样的东西。即使在双端处理器(例如ARM或MIPS)上运行Ubuntu,ELF可执行文件也始终是大(MSB)或小(LSB)字节。无法创建双端可执行文件,因此不需要运行时检查。
Fabel 2012年

4
要关闭此方法的优化,请使用“ volatile union ...”,它告诉编译器可以在其他地方更改“ u”,并且应该加载数据
mishmashru 2014年

1
为了使该函数在运行时返回与优化器正在计算的值不同的值,这将暗示优化器存在错误。您是说有一些编译的优化二进制代码示例,它们可以在不同字节序的两种不同体系结构上便携式运行,尽管优化器(在整个程序中)在编译过程中做出了明显的假设,但这些假设似乎至少与其中之一不兼容。建筑?
斯科特(Scott)

13

声明一个int变量:

int variable = 0xFF;

现在使用char *指针指向它的各个部分,并检查这些部分中的内容。

char* startPart = reinterpret_cast<char*>( &variable );
char* endPart = reinterpret_cast<char*>( &variable ) + sizeof( int ) - 1;

现在,取决于哪一个指向0xFF字节,您就可以检测字节序。这需要sizeof(int)> sizeof(char),但是对于所讨论的平台绝对是正确的。


8

有关更多详细信息,您可能需要查看此代码项目文章Endianness的基本概念

如何在运行时动态测试Endian类型?

如《计算机动画常见问题解答》中所述,您可以使用以下功能来查看您的代码是否在Little-Endian或Big-Endian系统上运行:收起

#define BIG_ENDIAN      0
#define LITTLE_ENDIAN   1
int TestByteOrder()
{
   short int word = 0x0001;
   char *byte = (char *) &word;
   return(byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN);
}

该代码将值0001h分配给16位整数。然后,将一个char指针分配为指向整数值的第一个(最低有效)字节。如果整数的第一个字节为0x01h,则系统为Little-Endian(0x01h位于最低或最低有效地址)。如果为0x00h,则系统为Big-Endian。


6

C ++的方式一直是使用boost,其中预处理器检查和强制类型转换在经过全面测试的库中分隔开来。

Predef库(boost / predef.h)识别四种不同的字节序

尾数图书馆,计划将提交给C ++标准,并支持尾数敏感数据的各种操作。

如以上答案所述,字节序将成为c ++ 20的一部分。


1
仅供参考,“四种不同的字节序”链接已断开,
Remy Lebeau,

固定并制作的Wiki
FuzzyTew

5

除非您使用已移植到PPC和Intel处理器的框架,否则您将必须进行条件编译,因为PPC和Intel平台具有完全不同的硬件体系结构,管线,总线等。这使得汇编代码之间完全不同他们俩。

关于查找字节序,请执行以下操作:

short temp = 0x1234;
char* tempChar = (char*)&temp;

您将获得tempChar为0x12或0x34,从中您将知道字节序。


3
这取决于不能保证恰好是2个字节。
sharptooth

3
不过,基于问题中给出的两种架构,这将是一个相当安全的选择。
敏2009年

8
在另一个平台上包含stdint.h并用于int16_t将来的证明以防不同。
Denise Skidmore

4

我会做这样的事情:

bool isBigEndian() {
    static unsigned long x(1);
    static bool result(reinterpret_cast<unsigned char*>(&x)[0] == 0);
    return result;
}

沿着这些思路,您将获得一个仅执行一次计算的省时功能。


你可以内联吗?不知道内联是否会导致多个静态变量的存储块
aah134 2013年

4

如上所述,使用联合技巧。

不过,上面建议的问题很少,最值得注意的是,对于大多数体系结构而言,未对齐的内存访问速度非常慢,而且,除非字对齐,否则某些编译器甚至根本不会识别这种常量谓词。

由于仅字节序测试很无聊,因此这里使用了(模板)函数,该函数将根据您的规范翻转任意整数的输入/输出,而与主机体系结构无关。

#include <stdint.h>

#define BIG_ENDIAN 1
#define LITTLE_ENDIAN 0

template <typename T>
T endian(T w, uint32_t endian)
{
    // this gets optimized out into if (endian == host_endian) return w;
    union { uint64_t quad; uint32_t islittle; } t;
    t.quad = 1;
    if (t.islittle ^ endian) return w;
    T r = 0;

    // decent compilers will unroll this (gcc)
    // or even convert straight into single bswap (clang)
    for (int i = 0; i < sizeof(r); i++) {
        r <<= 8;
        r |= w & 0xff;
        w >>= 8;
    }
    return r;
};

用法:

要将给定的字节序转换为主机,请使用:

host = endian(source, endian_of_source)

要将主机字节序转换为给定字节序,请使用:

output = endian(hostsource, endian_you_want_to_output)

生成的代码与在clang上编写手工汇编一样快,在gcc上则稍慢一些(展开的&,<<,>>,|每个字节),但仍然不错。


4
bool isBigEndian()
{
    static const uint16_t m_endianCheck(0x00ff);
    return ( *((uint8_t*)&m_endianCheck) == 0x0); 
}

1
这等效吗?#define IS_BIGENDIAN() (*((char*) &((int){ 0x00ff })) == (0x00))
伊曼纽尔

4

不要使用union

C ++不允许通过unions 进行类型修饰!
从不是最后写入的并集字段读取是未定义的行为
许多编译器都支持将其作为扩展,但是这种语言不能保证。

查看此答案以获取更多详细信息:

https://stackoverflow.com/a/11996970


只有两个有效答案可以保证可移植。

如果您有权访问支持C ++ 20的系统,则第一个答案
std::endian<type_traits>标头使用。

(在撰写本文时,C ++ 20尚未发布,但是除非碰巧影响到其std::endian包含,否则这将是从C ++ 20开始在编译时测试字节序的首选方法。)

C ++ 20起

constexpr bool is_little_endian = (std::endian::native == std::endian::little);

在C ++ 20之前,唯一有效的答案是存储一个整数,然后通过punning类型检查其第一个字节。
与使用unions 不同,C ++的类型系统明确允许这样做。

同样重要的是要记住,最佳的便携性static_cast,应使用,
因为reinterpret_cast是实现定义。

如果程序尝试通过除以下类型之一以外的glvalue来访问对象的存储值,则行为未定义:... a charunsigned chartype。

C ++ 11起

enum class endianness
{
    little = 0,
    big = 1,
};

inline endianness get_system_endianness()
{
    const int value { 0x01 };
    const void * address = static_cast<const void *>(&value);
    const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
    return (*least_significant_address == 0x01) ? endianness::little : endianness::big;
}

C ++ 11起(无枚举)

inline bool is_system_little_endian()
{
    const int value { 0x01 };
    const void * address = static_cast<const void *>(&value);
    const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
    return (*least_significant_address == 0x01);
}

C ++ 98 / C ++ 03

inline bool is_system_little_endian()
{
    const int value = 0x01;
    const void * address = static_cast<const void *>(&value);
    const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
    return (*least_significant_address == 0x01);
}

3
union {
    int i;
    char c[sizeof(int)];
} x;
x.i = 1;
if(x.c[0] == 1)
    printf("little-endian\n");
else    printf("big-endian\n");

这是另一种解决方案。与Andrew Hare的解决方案相似。


3

未经测试,但在我看来,这应该有效吗?因为它在小端上为0x01,在大端上为0x00?

bool runtimeIsLittleEndian(void)
{
 volatile uint16_t i=1;
 return  ((uint8_t*)&i)[0]==0x01;//0x01=little, 0x00=big
}

3

宣布:

我最初的帖子被错误地声明为“编译时间”。不是,在当前的C ++标准中甚至是不可能的。constexpr并不意味着该函数始终进行编译时计算。感谢Richard Hodges的纠正。

编译时,非宏,C ++ 11 constexpr解决方案:

union {
  uint16_t s;
  unsigned char c[2];
} constexpr static  d {1};

constexpr bool is_little_endian() {
  return d.c[0] == 1;
}

2
您是否在uint8_t上使用了无符号字符有特定的原因吗?
凯文

0运行时开销...我喜欢!
hanshenrik 2015年

我猜,这检测到构建机器的字节序,而不是目标?
2015年

2
这不是C ++的UB吗?
rr-

6
这在constexpr上下文中是不合法的。您无法访问尚未直接初始化的工会成员。没有预处理器魔术,就无法合法地在编译时检测字节序。
理查德·霍奇斯

2

您还可以通过预处理器使用boost标头文件(可以在boost endian中找到)进行此操作


1

除非endian标头仅适用于GCC,否则它将提供您可以使用的宏。

#include "endian.h"
...
if (__BYTE_ORDER == __LITTLE_ENDIAN) { ... }
else if (__BYTE_ORDER == __BIG_ENDIAN) { ... }
else { throw std::runtime_error("Sorry, this version does not support PDP Endian!");
...

不是这些__BYTE_ORDER____ORDER_LITTLE_ENDIAN____ORDER_BIG_ENDIAN__
Xeverous

1

如果您不希望条件编译,则可以编写字节序独立的代码。这是一个示例(摘自Rob Pike):

以字节序独立的方式读取磁盘上存储在little-endian中的整数:

i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);

相同的代码,尝试考虑机器的字节序:

i = *((int*)data);
#ifdef BIG_ENDIAN
/* swap the bytes */
i = ((i&0xFF)<<24) | (((i>>8)&0xFF)<<16) | (((i>>16)&0xFF)<<8) | (((i>>24)&0xFF)<<0);
#endif

好主意!现在,让我们通过网络套接字将整数传输到未知设备。
Maksym Ganenko,

@MaksymGanenko我没有收到您的评论。具有讽刺意味吗?我建议不要指定序列化数据的字节序。我建议不要根据接收数据的机器的字节顺序编写代码。
fjardon

@MaksymGanenko如果您不赞成,您可以解释为什么答案错误。至少可以帮助潜在读者理解为什么他们不应该听从我的回答。
fjardon '19


0

这个怎么样?

#include <cstdio>

int main()
{
    unsigned int n = 1;
    char *p = 0;

    p = (char*)&n;
    if (*p == 1)
        std::printf("Little Endian\n");
    else 
        if (*(p + sizeof(int) - 1) == 1)
            std::printf("Big Endian\n");
        else
            std::printf("What the crap?\n");
    return 0;
}

0

这是另一个C版本。它定义了一个宏,该宏wicked_cast()通过C99联合文字和非标准__typeof__运算符对内联类型进行修剪。

#include <limits.h>

#if UCHAR_MAX == UINT_MAX
#error endianness irrelevant as sizeof(int) == 1
#endif

#define wicked_cast(TYPE, VALUE) \
    (((union { __typeof__(VALUE) src; TYPE dest; }){ .src = VALUE }).dest)

_Bool is_little_endian(void)
{
    return wicked_cast(unsigned char, 1u);
}

如果整数是单字节值,则字节顺序没有意义,并且会生成编译时错误。


0

该方式的C编译器(至少我认识的人)的工作字节序在编译时决定。即使对于双端处理器(如ARM och MIPS),您也必须在编译时选择字节序。此外,在所有常见的可执行文件文件格式(例如ELF)中定义了字节序。尽管可以制作一个二进制的二进制代码(对于某些ARM服务器漏洞来说可能是?),但可能必须在汇编中完成。


-1

正如Coriiander指出的那样,此处的大多数(如果不是全部)代码将在编译时进行优化,因此生成的二进制文件在运行时不会检查“字节序”。

已经观察到,给定的可执行文件不应以两个不同的字节顺序运行,但是我不知道是否总是这样,并且对我而言似乎在编译时进行检查很麻烦。所以我编写了这个函数:

#include <stdint.h>

int* _BE = 0;

int is_big_endian() {
    if (_BE == 0) {
        uint16_t* teste = (uint16_t*)malloc(4);
        *teste = (*teste & 0x01FE) | 0x0100;
        uint8_t teste2 = ((uint8_t*) teste)[0];
        free(teste);
        _BE = (int*)malloc(sizeof(int));
        *_BE = (0x01 == teste2);
    }
    return *_BE;
}

MinGW无法优化此代码,即使它确实优化了此处的其他代码。我相信这是因为我将分配给较小字节内存的“随机”值保留为原来的状态(至少7位),因此编译器无法知道该随机值是什么,并且不会进行优化功能消失了。

我还对该函数进行了编码,以使检查仅执行一次,并且返回值存储用于下次测试。


为什么要分配4个字节以处理2个字节的值?为什么用掩盖不确定的值0x7FE?为什么要使用malloc()呢?那是浪费。而且_BE是(尽管很小的)内存泄漏和争用情况等待发生,动态缓存结果的好处是不值得的。我将static const uint16_t teste = 1; int is_little_endian() { return (0x01 == ((uint8_t*)&teste)[0]); } int is_big_endian() { return (0x01 == ((uint8_t*)&teste)[1]); }改为执行以下操作:简单有效,并且在运行时执行的工作要少得多。
雷米·勒博

@RemyLebeau,我的回答的全部重点是生成未经编译器优化的代码。当然,您的代码要简单得多,但是启用了优化功能后,编译后它将变成常量布尔值。正如我在回答中所说的,我实际上不知道是否有某种方式可以以相同的可执行文件在两个字节顺序上运行的方式来编译C代码,我也很好奇我是否可以在运行时进行检查尽管进行了优化。
Tex Killer

@TexKiller那么为什么不简单地禁用代码优化呢?使用volatile#pragma等等
雷米·勒博

@RemyLebeau,当时我还不知道那些关键字,我只是将其作为一个小挑战,以使用我所知道的来防止编译器优化。
Tex Killer

-1

虽然没有快速而标准的方法来确定它,但它会输出:

#include <stdio.h> 
int main()  
{ 
   unsigned int i = 1; 
   char *c = (char*)&i; 
   if (*c)     
       printf("Little endian"); 
   else
       printf("Big endian"); 
   getchar(); 
   return 0; 
} 

-1

请参见字节序 -C级代码插图。

// assuming target architecture is 32-bit = 4-Bytes
enum ENDIANNESS{ LITTLEENDIAN , BIGENDIAN , UNHANDLE };


ENDIANNESS CheckArchEndianalityV1( void )
{
    int Endian = 0x00000001; // assuming target architecture is 32-bit    

    // as Endian = 0x00000001 so MSB (Most Significant Byte) = 0x00 and LSB (Least     Significant Byte) = 0x01
    // casting down to a single byte value LSB discarding higher bytes    

    return (*(char *) &Endian == 0x01) ? LITTLEENDIAN : BIGENDIAN;
} 

-2

我正在阅读教科书:“ 计算机系统:程序员的观点”,而用C程序确定这是哪个字节序存在一个问题。

我使用了指针的功能来做到这一点,如下所示:

#include <stdio.h>

int main(void){
    int i=1;
    unsigned char* ii = &i;

    printf("This computer is %s endian.\n", ((ii[0]==1) ? "little" : "big"));
    return 0;
}

由于int占用4个字节,而char只占用1个字节。我们可以用一个字符指针,以指向INT值为1。因此,如果计算机是小端的字符字符指针指向的值为1,否则,其值应为0。


使用int32t可以改善这一点。
穿梭车

1
^如果您想挑剔,最好的方法是int16_fast_t。和@ Archimedes520的当前代码无法在int本身为int8;的拱形上使用;)(不过,这可能首先违反了c标准)
hanshenrik 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.