在循环中的什么时候整数溢出会变成未定义的行为?


86

这是一个示例来说明我的问题,其中涉及一些我无法在此处发布的更复杂的代码。

#include <stdio.h>
int main()
{
    int a = 0;
    for (int i = 0; i < 3; i++)
    {
        printf("Hello\n");
        a = a + 1000000000;
    }
}

该程序在我的平台上包含未定义的行为,因为a它将在第3个循环中溢出。

这是否会使整个程序具有未定义的行为,还是仅在实际发生溢出之后?编译器是否可以解决a 导致溢出的问题,以便它可以声明整个循环未定义,并且即使它们都在溢出之前发生,也不必费心运行printfs?

(尽管标记为C和C ++有所不同,但如果它们不相同,我会对两种语言的答案都感兴趣。)


7
想知道编译器是否可以解决a未使用的问题(计算本身除外),然后删除a
4386427,2016年

12
您可能会喜欢今年的CppCon上的My Little Optimizer:未定义行为是不可思议的。这完全取决于编译器可以根据未定义的行为进行哪些优化。
TartanLlama



Answers:


108

如果您对纯粹的理论答案感兴趣,则C ++标准允许使用未定义的行为进行“时间旅行”:

[intro.execution]/5: 执行格式正确的程序的合格实现应产生与具有相同程序和相同输入的抽象机相应实例的可能执行之一相同的可观察行为。但是,如果任何这样的执行包含未定义的操作,则此国际标准对使用该输入执行该程序的实现没有任何要求(即使对于第一个未定义的操作之前的操作也没有要求)

这样,如果您的程序包含未定义的行为,则整个程序的行为将是未定义的。


4
@KeithThompson:但是,该sneeze()函数本身在该类的任何东西上都是未定义的Demon(其中鼻部变种是其子类),从而使整个东西仍然是循环的。
塞巴斯蒂安·莱纳托维奇

1
但是printf可能不会返回,因此定义了前两轮,因为直到完成后,尚不清楚会有UB。参见stackoverflow.com/questions/23153445/…–
usr

1
这就是为什么编译器在技术上是它的权利范围内发出“NOP” Linux内核(因为引导代码依赖于未定义行为):blog.regehr.org/archives/761
Crashworks

3
@Crashworks这就是为什么Linux以不可移植的C语言编写并编译为不可移植的C语言的原因(即C语言的超集,它需要具有特定选项的特定编译器,例如-fno-strict-aliasing)
user253751 '16

3
@usr我希望它已定义,如果printf不返回,但如果printf要返回,则未定义的行为会在printf调用之前引起问题。因此,时间旅行。printf("Hello\n");然后下一行编译为undoPrintf(); launchNuclearMissiles();
user253751 '16

31

首先,让我更正这个问题的标题:

未定义行为不是(特别是)执行领域。

未定义行为会影响所有步骤:编译,链接,加载和执行。

请记住一些示例,以确保这一点:

  • 编译器可以假定从未执行包含未定义行为的代码部分,因此假定导致它们的执行路径为无效代码。请参阅克里斯·拉特纳(Chris Lattner),每个C程序员应该了解的未定义行为
  • 链接器可以假定存在一个弱符号(通过名称识别)的多个定义时,由于一个定义规则,所有定义都是相同的
  • 加载程序(如果使用动态库的话)可以假定相同,从而选择它找到的第一个符号;通常用于LD_PRELOAD在Unixes上使用技巧来拦截呼叫
  • 如果您使用悬空指针,执行可能会失败(SIGSEV)

这就是未定义行为的可怕之处:几乎不可能提前预测会发生什么确切的行为,并且每次工具链,底层操作系统更新时都必须重新考虑这种预测。


我建议观看Michael Spencer(LLVM开发人员)的这段视频:CppCon 2016:My Little Optimizer:Undefined Behavior is Magic


3
这就是让我担心的。在我的真实代码中,它很复杂,但是我可能会遇到一个总是溢出的情况。我并不十分在乎,但是我担心“正确”的代码也会受到此影响。显然,我需要修复它,但是修复需要理解:)
jcoder

8
@jcoder:这里有一个重要的逃生途径。不允许编译器猜测输入数据。只要至少有一个输入未发生“未定义行为”,编译器就必须确保该特定输入仍能产生正确的输出。关于危险性优化的所有令人恐惧的说法仅适用于不可避免的UB。实际上,如果使用argc循环计数,则这种情况argc=1不会产生UB,并且编译器将不得不处理该事件。
MSalters

@jcoder:在这种情况下,这不是无效代码。但是,编译器可能足够聪明,可以推断出它i不能增加N多次,因此其值是有界的。
Matthieu M.

4
@jcoder:如果f(good);做了一些事情,X和f(bad);调用未定义的行为,那么一个程序,只是所调用f(good);保证做X,但f(good); f(bad);并不保证做到十

4
@Hurkyl更有趣的是,如果您的代码是if(foo) f(good); else f(bad);,智能编译器将放弃比较并产生无条件的foo(good)
约翰·德沃夏克 John Dvorak)

28

积极地针对16位的C或C ++编译器int知道添加1000000000int类型的行为是不确定的

它是由两种标准允许做任何它想做这可能包括整个程序的缺失,留下int main(){}

但是更大的呢 ints呢?我还不知道执行此操作的编译器(无论如何我都不是C和C ++编译器设计的专家),但是我想有时候某个面向32位int或更高版本的编译器会发现该循环是无限的(i不会改变)因此a最终会溢出。因此,它可以再次将输出优化为int main(){}。我在这里要说明的一点是,随着编译器优化逐渐变得更加积极,越来越多的未定义行为构造以意想不到的方式表现出来。

循环无限的事实本身并不是不确定的,因为您正在循环主体中写入标准输出。


3
标准是否允许在未定义行为出现之前做任何想要的事情?这在哪里说明?
jimifiki

4
为什么是16位?我猜OP正在寻找32位带符号的溢出。
4386427

8
@jimifiki在标准中。C ++ 14(N4140)1.3.24“未定义的行为=本国际标准不施加任何要求的行为”。加上详尽的音符。但关键是,未定义的不是“语句”的行为,而是程序的行为这意味着,只要UB是由标准中的规则触发(或由于没有规则而触发),则该标准将不再适用于整个程序因此,程序的任何部分都可以按其期望的方式运行。
Angew不再为

5
第一个陈述是错误的。如果int为16位,则将在定义明确的位置long(因为文字操作数的类型为long)进行加法,然后通过实现定义的转换将其转换为int
R .. GitHub停止帮助ICE

2
@usr的行为printf由标准定义为始终返回
MM

11

从技术上讲,在C ++标准下,如果程序包含未定义的行为,则即使在编译时甚至在程序执行之前),整个程序的行为也是未定义的。

在实践中,由于编译器可能会(作为优化的一部分)假定不会发生溢出,因此尽管在循环的第三次迭代(假设是32位计算机)上,至少程序的行为是不确定的您可能会在第三次迭代之前获得正确的结果。但是,由于整个程序的行为在技术上是不确定的,因此无法阻止该程序生成完全不正确的输出(包括无输出),在运行期间的任何时候在运行时崩溃,甚至无法完全编译(因为未定义的行为会扩展到编译时间)。

未定义行为为编译器提供了更多优化空间,因为它们消除了有关代码必须执行的某些假设。这样做不能保证依赖于涉及未定义行为的假设的程序可以按预期工作。因此,您不应依赖C ++标准认为未定义的任何特定行为。


如果UB部分在if(false) {}范围内怎么办?由于编译器假设所有分支都包含〜定义明确的逻辑部分,并因此基于错误的假设进行操作,是否会使整个程序中毒?
mlvljr

1
该标准对未定义的行为没有任何要求,因此从理论上讲,是的,它确实使整个程序中毒。但是,实际上,任何优化的编译器都可能会删除死代码,因此对执行没有影响。但是,您仍然不应该依赖此行为。
bwDraco '16

众所周知,thanx :)
mlvljr

9

要了解为什么未定义的行为可以像@TartanLlama那样恰当地“时间旅行”,让我们看一下“假设”规则:

1.9程序执行

1本国际标准中的语义描述定义了参数化的不确定性抽象机器。本国际标准对一致性实现的结构没有要求。特别是,它们不需要复制或模拟抽象机的结构。相反,需要遵循的实现来(仅)模拟抽象机的可观察行为,如下所述。

这样,我们可以将程序视为带有输入和输出的“黑匣子”。输入可以是用户输入,文件和许多其他内容。输出是标准中提到的“可观察到的行为”。

该标准仅定义了输入和输出之间的映射,没有别的。它通过描述一个“示例黑匣子”来做到这一点,但明确表示具有相同映射的任何其他黑匣子同样有效。这意味着黑匣子的内容无关紧要。

考虑到这一点,说不确定的行为在某个时刻发生是没有意义的。在黑匣子的示例实现中,我们可以说出发生的时间和地点,但是实际黑匣子可能完全不同,因此我们无法再说出它在何时何地发生。从理论上讲,编译器可以例如决定枚举所有可能的输入,并预先计算结果输出。这样,未定义的行为将在编译期间发生。

未定义的行为是输入和输出之间的映射不存在。对于某些输入,程序可以具有未定义的行为,而对于其他输入,则可以具有定义的行为。这样,输入和输出之间的映射就是不完整的。存在没有映射到输出的输入。
问题中的程序对于任何输入都具有未定义的行为,因此映射为空。


6

假设int是32位,则在第三次迭代时发生未定义的行为。因此,例如,如果循环仅在第三次迭代之前有条件地达到或可以有条件地终止,那么除非实际到达第三次迭代,否则就不会有未定义的行为。但是,如果行为未定义,则程序的所有输出都是不确定的,包括相对于调用未定义行为而言“过去”的输出。例如,对于您而言,这意味着不能保证在输出中看到3条“ Hello”消息。


6

TartanLlama的答案是正确的。未定义的行为可能随时发生,即使在编译期间也可能发生。这似乎很荒谬,但这是允许编译器执行所需操作的关键功能。成为编译器并不总是那么容易。您必须每次都严格按照规范的要求进行操作。但是,有时很难证明正在发生某种特定行为。如果您还记得停顿问题,那么在开发软件时就显得微不足道了,您无法证明该问题在馈入特定输入后是否完成或进入无限循环。

我们可能会使编译器变得悲观,并在担心下一条指令可能是诸如问题之类的停顿问题之一的情况下不断进行编译,但这是不合理的。相反,我们让编译器通过了:在这些“未定义的行为”主题上,它们没有任何责任。未定义的行为包含所有如此微妙的行为,以至于我们很难将它们与真正令人讨厌的停止问题隔离开来。

有一个我喜欢发布的示例,尽管我承认我失去了来源,所以我必须换个说法。它来自特定版本的MySQL。在MySQL中,它们有一个循环缓冲区,其中填充了用户提供的数据。他们当然想确保数据不会溢出缓冲区,所以他们进行了检查:

if (currentPtr + numberOfNewChars > endOfBufferPtr) { doOverflowLogic(); }

看起来足够理智。但是,如果numberOfNewChars确实很大并且溢出怎么办?然后它环绕并变成小于的指针endOfBufferPtr,因此永远不会调用溢出逻辑。因此,他们在此之前添加了第二项检查:

if (currentPtr + numberOfNewChars < currentPtr) { detectWrapAround(); }

看起来您已经处理了缓冲区溢出错误,对吗?但是,提交了一个错误,指出该缓冲区在特定版本的Debian上溢出了!仔细的调查表明,此版本的Debian是第一个使用特别先进的gcc版本的版本。在此版本的gcc上,编译器认识到currentPtr + numberOfNewChars永远不能小于currentPtr,因为指针的溢出是未定义的行为!这足以使gcc优化整个检查,并且即使您编写了代码来检查它,突然间您也无法防止缓冲区溢出

这是规范行为。一切都是合法的(尽管据我所知,gcc在下一版本中回滚了此更改)。这不是我认为的直观行为,但是如果您稍加扩展想象力,就很容易看到这种情况的微小变化如何可能会成为编译器的停顿问题。因此,规范编写者将其定义为“未定义行为”,并表示编译器可以绝对满足其要求。


我不认为特别令人惊讶的编译器有时会表现出对超出“ int”范围的类型执行有符号算术运算,尤其是考虑到即使在x86上直接生成代码,有时这样做也比截断中间代码更有效结果。更令人惊讶的是,当溢出影响其他计算时,即使代码将两个uint16_t值的乘积存储到uint32_t中,这种情况也可能在gcc中发生-该操作应该没有合理的理由在非消毒的构建中采取令人惊讶的行动。
超级猫

当然if(numberOfNewChars > endOfBufferPtr - currentPtr),如果numberOfNewChars永远不能为负,并且currentPtr始终指向缓冲区中某个不需要甚至不需要“绕回”检查的位置,则正确的检查将是。(我认为您提供的代码没有希望在循环缓冲区中工作-在解释中您已省略了该代码的必要内容,因此我也忽略了这种情况)
Random832

@ Random832我确实省了一吨。我试图引用更大的上下文,但是由于我失去了资料来源,因此我发现对上下文的解释使我陷入了更多麻烦,因此我将其排除在外。我真的需要找到爆炸的错误报告,以便正确引用它。这确实是一个强大的示例,说明了如何以一种方式编写代码并以完全不同的方式进行编译。
Cort Ammon

这是我最大的不确定行为问题。它有时使您无法编写正确的代码,并且当编译器检测到它时,默认情况下不会告诉您它触发了未定义的行为。在这种情况下,用户只是想进行算术运算(无论是否使用指针),并且编写安全代码的所有辛苦工作都被撤消。至少应该有一种方法来注释一段代码来说-这里没有花哨的优化。C / C ++在太多关键区域中使用,以使这种危险情况继续存在,有利于优化
John McGrath

4

除了理论上的答案外,一个实践的观察是,很长一段时间以来,编译器已经对循环进行了各种变换,以减少循环中的工作量。例如,给定:

for (int i=0; i<n; i++)
  foo[i] = i*scale;

编译器可能会将其转换为:

int temp = 0;
for (int i=0; i<n; i++)
{
  foo[i] = temp;
  temp+=scale;
}

从而节省了每次循环迭代的乘法。编译器以不同程度的攻击性进行了调整的另一种优化形式会将其转化为:

if (n > 0)
{
  int temp1 = n*scale;
  int *temp2 = foo;
  do
  {
    temp1 -= scale;
    *temp2++ = temp1;
  } while(temp1);
}

即使在溢出时进行静默环绕的机器上,如果小于n的某个数也可能会发生故障,将其乘以小数会产生0。如果从内存中多次读取了小数位,也可能会陷入无尽的循环。意外更改了它的值(在任何情况下,如果“ scale”可以在不调用UB的情况下更改中间循环,则不允许编译器执行优化)。

尽管在将两个短无符号类型相乘以得到介于INT_MAX + 1和UINT_MAX之间的值的情况下,大多数此类优化不会有任何麻烦,但是gcc在某些情况下,循环内的这种相乘可能会导致循环提早退出。 。我没有注意到这种行为是由生成的代码中的比较指令引起的,但是在编译器使用溢出来推断循环最多可以执行4次或更少的情况下,这种现象是可以观察到的。在某些输入会导致UB而其他输入不会导致UB的情况下,即使它的推断导致忽略了循环的上限,它也不会默认生成警告。


4

根据定义,未定义的行为是灰色区域。您根本无法预测它将做什么或将不会做什么-这就是“未定义的行为”的含义

自远古时代以来,程序员就一直试图从不确定的情况中挽救确定性的残余。他们有一些他们真正想要使用的代码,但是结果却是不确定的,所以他们试图争辩:“我知道它是未定义的,但是肯定会在最坏的情况下执行此或该操作;它将永远不会这样做。” 有时这些论点或多或少是正确的-但通常,它们是错误的。随着编译器变得越来越聪明(或者有人会说越来越狡猾),问题的界限不断变化。

因此,实际上,如果您想编写保证能够正常工作并且可以长时间工作的代码,则只有一种选择:不惜一切代价避免出现不确定的行为。确实,如果您涉足其中,它将再次困扰您。


然而,事情就在这里...编译器可以使用未定义的行为进行优化,但是它们通常不会告诉您。因此,如果我们有一个如此出色的工具,您必须不惜一切代价避免执行X,为什么编译器无法向您发出警告,以便您进行修复?
杰森S

1

您的示例不考虑的一件事就是优化。 a是在循环中设置的,但从未使用过,优化器可以解决该问题。因此,优化器a完全丢弃是合理的,在这种情况下,所有未定义的行为都将像布儒姆的受害者一样消失。

但是,这当然是不确定的,因为优化是不确定的。:)


1
在确定行为是否未定义时,没有理由考虑优化。
基思·汤普森

2
程序表现为可能会假定它应为事实这一事实并不意味着未定义的行为“消失”。行为仍未定义,您只是在依靠运气。程序的行为可以基于编译器选项进行更改的事实非常有力地表明了该行为未定义。
乔丹·梅洛

@JordanMelo由于之前的许多答案都讨论了优化(以及OP对此特别提出的问题),因此我提到了优化的功能,而以前的答案均未涉及。我还指出,即使优化可以删除它,但仍无法定义对优化以任何特定方式运行的依赖。我当然不推荐!:)
格雷厄姆

@KeithThompson可以,但是OP特别询问了优化及其对他在平台上看到的未定义行为的影响。具体的行为可能会消失,具体取决于优化。正如我在回答中所说的那样,不确定性不会。
格雷厄姆

0

由于此问题是双重标记的C和C ++,因此我将尝试同时解决这两个问题。C和C ++在这里采用不同的方法。

在C语言中,实现必须能够证明将调用未定义的行为,以便将整个程序视为具有未定义的行为。在OP的示例中,编译器证明这一点似乎微不足道,因此就好像整个程序都未定义一样。

我们可以从缺陷报告109中看到这一点,它的症结在于:

但是,如果C标准认识到“未定义的值”的单独存在(其创建不完全涉及“未定义的行为”),那么进行编译器测试的人员可以编写如下的测试用例,并且他/她也可以期望(或可能要求)合格的实现至少应编译该代码(并可能还允许其执行)而不会出现“失败”。

int array1[5];
int array2[5];
int *p1 = &array1[0];
int *p2 = &array2[0];

int foo()
{
int i;
i = (p1 > p2); /* Must this be "successfully translated"? */
1/0; /* Must this be "successfully translated"? */
return 0;
}

因此,最重要的问题是:上面的代码是否必须“成功翻译”(意味着什么)?(参见5.1.1.3节的脚注。)

响应是:

C标准使用术语“不确定值”而非“不确定值”。使用不确定值的对象会导致不确定的行为。条款5.1.1.3的脚注指出,只要有效的程序仍能正确翻译,实现就可以自由生成任何数量的诊断信息。 如果在需要常量表达式的上下文中出现其撤回将导致未定义行为的表达式,则包含程序并不严格符合。此外,如果给定程序的所有可能执行都会导致未定义的行为,则该给定程序不是严格符合要求的。 合格的实现一定不能仅仅因为某个程序的某些可能执行会导致不确定的行为而翻译严格合格的程序。因为可能永远不会调用foo,所以给定的示例必须通过符合标准的实现成功翻译。

在C ++中,该方法似乎更宽松,并且将建议程序具有未定义的行为,而不管实现是否可以静态证明它。

我们有[intro.abstrac] p5,其中说:

执行格式正确的程序的合格实现应产生与具有相同程序和相同输入的抽象机相应实例的可能执行之一相同的可观察行为。但是,如果任何这样的执行包含未定义的操作,则本文档对使用该输入执行该程序的实现没有任何要求(甚至不涉及第一个未定义的操作之前的操作)。


如果在给定特定输入的情况下至少有一个可能的程序执行会调用UB,则执行功能会调用UB的事实只会影响程序在给定特定输入时的行为方式。调用函数将调用UB的事实并不能阻止程序在被馈入不允许调用该函数的输入时定义行为。
超级猫

@supercat我相信这至少是我对C的回答。
Shafik Yaghmour '18

我认为对于C ++引用的文本也是如此,因为短语“任何这样的执行”是指程序可以使用特定的给定输入执行的方式。如果某个特定的输入不能导致函数执行,我在引号中看不到任何内容表明该函数中的任何内容都会导致UB。
超级猫

-2

最佳答案是一个错误(但很常见)的误解:

未定义的行为是运行时属性*。它不能“时间旅行”!

某些操作(根据标准)定义为具有副作用,无法进行优化。执行I / O或访问volatile变量的操作属于此类别。

但是,有一个警告:UB可以是任何行为,包括撤消先前操作的行为。在某些情况下,这可能会对优化早期代码产生类似的结果。

实际上,这与最佳答案中的引用(强调我的)一致:

执行格式正确的程序的合格实现应产生与具有相同程序和相同输入的抽象机相应实例的可能执行之一相同的可观察行为。
但是,如果任何这样的执行包含未定义的操作,则此国际标准对使用该输入执行该程序的实现没有任何要求(甚至不涉及第一个未定义的操作之前的操作)。

是的,此引号确实“甚至不涉及第一个未定义操作之前的操作”,但请注意,这特别是与正在执行的代码有关,而不仅仅是编译。
毕竟,实际上未达到的未定义行为不会做任何事情,并且要实际到达包含UB的行,必须先执行它之前的代码!

因此,是的,一旦执行了UB,先前操作的任何效果都将变得不确定。但是在此之前,程序的执行是明确定义的。

但是请注意,可以将导致这种情况的程序的所有执行优化为等效程序,包括执行先前操作但撤消其效果的程序。因此,只要这样做,前面的代码就可能被优化掉,这等同于取消它们的效果。否则,不能。参见以下示例。

*注:这是不是不一致UB发生在编译时。如果编译器确实可以证明始终对所有输入执行UB代码,则UB可以延长编译时间。但是,这需要知道所有先前的代码最终都会返回,这是一个很强的要求。同样,请参见下面的示例/说明。


要具体说明,请注意以下代码必须打印foo并等待您的输入,而不管其后发生的任何未定义行为:

printf("foo");
getchar();
*(char*)1 = 1;

但是,还请注意,不能保证foo在UB发生后仍会保留在屏幕上,或者不能保证您键入的字符将不再位于输入缓冲区中。这两个操作都可以“撤消”,与UB“时间旅行”具有相似的效果。

如果getchar()行是不存在,这是合法的线路被优化掉,当且仅当那将是没有区别的输出foo,然后“非做”吧。

两者是否难以区分将完全取决于实现(即,取决于编译器和标准库)。例如,您可以在等待另一个程序读取输出时在这里printf 阻塞线程吗?还是会立即返回?

  • 如果它可以在此处阻塞,则另一个程序可以拒绝读取其完整输出,并且它可能永远不会返回,因此UB可能永远不会出现。

  • 如果它可以立即返回此处,那么我们知道它必须返回,因此,对其进行优化与执行然后取消其效果完全没有区别。

当然,由于编译器知道其特定版本允许的行为printf,因此可以进行相应的优化,因此printf在某些情况下可能会优化,而在其他情况下则无法优化。但是,再次有理由证明,这与UB取消先前的操作是无法区分的,不是因为UB而使先前的代码“中毒了”。


1
您完全误解了标准。它说执行程序时的行为是不确定的。期。这个答案是100%错误的。该标准非常明确-未定义运行带有输入的程序的输入,该输入在幼稚的执行流程中的任何时候都会生成UB。
David Schwartz

@DavidSchwartz:如果您按照其逻辑结论做出解释,您应该意识到这没有逻辑意义。程序启动时输入不是完全确定的。在任何给定的行中,程序的输入(甚至仅仅是其存在)都允许依赖于程序的所有副作用,直到该行为止。因此,该程序无法避免产生UB系列之前的副作用,因为这需要与其环境进行交互,并因此影响是否首先达到UB系列。
user541686

3
没关系 真。同样,您只是缺乏想象力。例如,如果编译器可以说没有兼容的代码可以分辨出差异,那么它可以移动UB的代码,从而使UB的部分在您天真希望“在前”的输出之前执行。
David Schwartz

2
@Mehrdad:也许更好的说法是说UB不能回溯到最后一点,在现实世界中可能已经发生的事情已经定义了行为。如果实现可以通过检查输入缓冲区来确定对getchar()的下一个1000次调用中的任何一个都不可能阻塞,并且还可以确定在第1000个调用之后发生UB,则不需要执行任何以下操作:电话。但是,如果某个实现要指定执行,直到所有之前的输出都通过了,否则执行将不通过getchar()...
supercat

2
...被传递到300波特的终端,并且在此之前发生的任何control-C都会导致getchar()发出信号,即使在其前面的缓冲区中还有其他字符时,这样的实现也无法将所有UB移到getchar()之前的最后一个输出之后。棘手的是,要知道在什么情况下应该期望编译器通过程序员,因此,库实现可能会提供超出标准要求的行为保证。
超级猫
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.