在c中,i+=1;是原子的吗?
- 请检查这一点以了解一些观点:stackoverflow.com/questions/652788/…
C标准没有定义它是否是原子的。
在实践中,如果给定的操作是原子操作,则永远不会编写失败的代码,但是如果给定的操作不是原子操作,则很可能编写失败的代码。因此假设不是这样。
- 这个答案比现在被投赞成票的答案更有意义(来自费尔南德斯先生)
- 同意,尽管我正在开始一个经验法则,"i+=1";对于可移植性来说不是原子的。
- @疯狂:没错。如果您假设它不是原子的,但是它是原子的,那么假设不会带来任何伤害,除非可能存在一些平台,您正在执行不必要的锁定。正确的做法可能是在可移植性层中定义自己的原子增量函数(或宏)。然后在Windows上它会调用InterlockedCrement,在嵌入式平台上它可能会禁用增量周围的中断等。默认的POSIX可移植实现可能使用全局信号量,必须安全地初始化。奇迹的平台只是在增加。
- 如果C标准没有说明,那么OP问题的答案是"不"。回答结束。这就像是问i+=1是否是碳中性的。如果标准是沉默的,那么答案一定是否定的。如果某些实现碰巧确保CPU为增量期间生成的每个二氧化碳分子吸收一个二氧化碳分子,那么这并不意味着,在C中,增量是碳中性的,甚至在C中,增量可能是碳中性的。这只意味着有一天,一些环境学家的编撰者疯了。:)
- 没错。但是"它是原子的"这个问题的答案是"不","它是非原子的"这个问题的答案也是"不"。我没有表现得像一个神秘的神谕,试图欺骗求婚者误解问题的答案,而是比严格要求的问题钻得更深一点。这不是一个智力测验节目,也不是一个恶意的盘问,我要求有权给出多于是/否的答案;-)
- @Steve,在考虑语言规范的语义时应该这样做。+从我这里。
- 这进一步被"非原子性"定义为可能发生的事情,而不是肯定发生的事情所混淆。因此,如果一个实现定义了"增量是原子的",我想您可能会认为,尽管增量是原子的,但它也是非原子的,因为它并不与任何定义的非原子操作行为相矛盾。所以第二个问题的答案应该是"是"。但不管怎样,我试图用一种解释这种情况的方式来回答。您不能确定编译器不会发出增量的原子insn,但我敢打赌它不会。
- 原子的意思是不可分割的(来自希腊原子)。因此,非原子的意思应该是可除的。我认为如果"增量是原子的",就不是"非原子的"。
- 要添加的一件事是:执行原子增量有特定于平台的函数。在Windows下,查看联锁功能。
不。
C语言标准保证原子的唯一操作是向sig_atomic_t类型的变量分配或检索值,该变量在中定义。
(C99,第7.14章信号处理。)
- 同样,并非完全正确:取决于编译器的优化和能力。
- 我从来没有听说过这种类型,但一个(简短的)谷歌搜索似乎表明它只能被原子地访问。我明白吗?
- @杜邦:绝对正确。问题是操作是否是C语言中的原子操作,而不是特定的平台/编译器设置。语言声明原子性的唯一地方是sig_atomic_t,因此您必须假设任何其他操作都是not原子以保证安全。
- @Roel:标准规定访问这种类型应该是原子的;我读这个是指使用操作符"="来设置或检索一个值,因为如果你能做的只是从中读取值,那么它就有点无用了。
- +1:当然是最好的答案。这里的其他人都在抽烟或者别的什么。问题是关于C(在某些体系结构的某些实现中,增量是否是原子的)。你是对的,德夫索拉,答案是不!在C中,它不是原子的。如果它碰巧编译到某个平台上的原子操作,那不是因为C标准使它成为原子操作,而是因为实现实现做到了。
- @丹:我理解"在C中"是指"在符合C的实现中"。我不认识C语言柏拉图理想的效用,在这种理想中,对于所有整数n,"i n t的宽度为n位是错误的",同时,"存在一个整数n,使得int的宽度为n位"。因此,归纳和概括的逻辑规则不适用。"在c中,"int"可以是32位的宽度。另外,它"可能"的宽度为23位。在c中,增量"可能是"原子量。如果我说"在C中"有错误的话,我的意思是这是被禁止的,而不是不需要的。对不起,如果那是一种强烈的幻觉。
- @史蒂夫:不,在C语言中,增量不是原子的。如果需要原子操作,请使用平台的相应API。依赖于实现定义的行为(可能会随编译器选项或版本而改变,恕不另行通知)是痛苦的道路。"这种柏拉图式的C语言理想"的价值在于,当你的同事在不同的机器上编译代码时,你的代码不会毫无明显原因地随机中断。在那里,做了大约十年。
- 是的,我非常熟悉编写可移植代码。在C语言中,有符号整数类型不是1的补码,这一点是否同样坚定?我更愿意说,在C语言中,有符号表示可能是1的补码,以避免暗示2值逻辑不合适。我没有注意到它会影响我的代码的质量或可移植性,使其以需求和禁止实现的方式表达标准,而不是"在C中"使用正确或错误的语句。只要你知道这对你的程序意味着什么,我认为我的方法就不太可能误导你。
- 你的意思是2的补码吗?;-)
- 1的补码就是我想说的,但当然,这同样适用于2的补码:"在c中,有符号整数可以是2的补码";"在c中,有符号整数可以是符号数量"。实际上,我通常不会说整数"可能是"2的补码,我会说它们"几乎总是"2的补码。不过,这并不是关于标准的声明,而是对当前可用的C实现的回顾。同样,增量几乎总是可以中断的。
- 注意,c99标准只保证在异步信号处理程序中,sig_stomic_t对象只能(以定义良好的方式)写入。未定义从异步信号处理程序中的sig_atomic_t对象读取。因此,无论如何,在这个特定的案例中,i += 1不会被很好地定义(尽管i = 1会被定义)。
- @史蒂夫:在说有符号整数可以是一种类型对另一种类型,或者说增量可以是原子的,仅仅是因为标准没有说一种或另一种?我们是否应该假定,当您取消引用一个空指针时,编译器可能会在您的头上撒下橡皮小鸡,仅仅是因为标准没有说它不会这样做?如果你写一个依赖于下雨的橡胶鸡的程序,那么你会有一个严重的可移植性问题,不是吗?所以为什么不直接说"不,橡皮塞不会下雨"。同样,"不,增量不是原子的"。
- "有符号整数可以是一种类型,而不是另一种。"为了区分这三种可能的情况:(a)标准要求整数是1的补码(我们说,"整数是1的补码";(b)标准要求整数不是1的补码(我说,"整数不是1的补码")(c)标准既不要求也不禁止(我说,"整数可能是1的Ccomplement",你说"整数不是1的补码")。有关处理与C标准的3值逻辑类似的5值逻辑的术语,请参阅RFC2119。
- 说橡皮鸡"不会"下雨是不对的。他们可能会,也可能不会。你不能指望他们下雨,也不能指望他们不下雨,所以说他们不下雨是极为误导的。(就标准而言具有误导性。显然,您可以根据自己对C实现的了解做出精明的预测,但这是完全不同的说法)。如果有人看到我说,"C中的整数可能是1的补码",并且写依赖于它们的代码,那么我认为这显然是他们的错误。他们应该阅读RFC2119。
- 我要说的是,没有人应该鼓励其他人期望橡皮鸡可能会下雨(尽管理论上是可能的),因为依赖这种行为显然是愚蠢的。原子增量也是如此。
- 据我所知,我对你的回答没有异议。只要有机会,我就喜欢提起橡皮鸡的话题;)
- @史蒂夫:我明白你的意思了。但我见过太多的开发人员被"但它在这里起作用"所咬,所以我变得有点偏执,倾向于制定绝对的方案。丹喜欢橡皮鸡的地方,我经常告诉人们"不明确的行为"意味着他们的硬盘会被删除。;-)所以您是对的,正确的语句是C,增量不能保证是原子的。我仍然认为更具启发性的说法是我所作的:它不是原子的。出于所有实际目的。
- 是的,在这种情况下,"它不是原子的"工作得很好,因为正如我在另一篇评论中所说,没有任何非原子的行为,结果会有人错误地依赖于它。我所不喜欢的是,一般来说,把C语言中发生的事情分为"真"和"假",因为中间有一个很大的"未指定"的区域。如果有人想要删除一个硬盘,那么不定义的行为是否意味着他们的硬盘"将"被删除,或者"可能"被删除,这对他们来说是一个很大的区别,由实现决定。
在C中定义,可能在实践中没有。把它写在汇编中。
标准没有保证。
因此,一个可移植的程序不会做出这样的假设。不清楚您的意思是"必须是原子的",还是"在我的C代码中恰好是原子的",第二个问题的答案是,它取决于很多事情:
并不是所有的机器都有一个递增的内存操作。有些机器需要加载和存储该值,以便对其进行操作,因此答案是"从不"。
在具有增量内存操作的计算机上,不能保证编译器不会输出加载、增量和存储序列,也不能使用其他非原子指令。
在具有增量内存操作的计算机上,相对于其他CPU单元,它可能是原子的,也可能不是原子的。
在具有原子增量内存操作的计算机上,它可能不被指定为体系结构的一部分,而只是CPU芯片的特定版本的属性,甚至只是某些核心逻辑或主板设计的属性。
至于"我如何原子地做到这一点",通常有一种方法可以快速做到这一点,而不是诉诸于(更昂贵的)协商互斥。有时这涉及到特殊的冲突检测可重复的代码序列。最好在汇编语言模块中实现这些,因为它是特定于目标的,所以对HLL没有可移植性好处。
最后,由于不需要(昂贵的)协商互斥的原子操作速度快,因此很有用,而且在任何情况下,对于可移植代码来说,系统通常都有一个库,通常用汇编语言编写,它已经实现了类似的功能。
- +1.详细解释
- +1用于提及原子WRT其他CPU单元。
表达式是否是原子的,只取决于编译器生成的机器代码以及它将运行的CPU体系结构。除非加法可以在一个机器指令中实现,否则它不太可能是原子的。
如果您使用的是Windows,那么您可以使用interlockedincrement()API函数来执行保证的原子增量。有类似的减量函数等。
虽然对于C语言我可能不是原子的,但是应该注意,在大多数平台上,这是原子的。GNU C库文档说明:
In practice, you can assume that int and other integer types no longer than int are atomic. You can also assume that pointer types are atomic; that is very convenient. Both of these assumptions are true on all of the machines that the GNU C library supports and on all POSIX systems we know of.
号
- 注意这里的"原子"是指原子读和原子写。它不适用于i+=1。
- 关于这个问题,在GCC上你可以做__sync_add_and_fetch(&i, 1)。当然不是便携式的,但它能工作!
- @lnxprgr3:不仅在GCC中,而且在Clang中。
这真的取决于你的目标和你的UC/处理器的记忆设置。如果我是一个保存在寄存器中的变量,那么它可能是原子的。
问题的答案取决于i是局部变量、static还是全局变量。如果i是static或全局变量,那么不,说明i += 1不是原子变量。但是,如果i是一个局部变量,那么对于在x86体系结构上运行的现代操作系统和其他体系结构来说,该语句是原子的。@丹·克里斯托洛沃对于局部变量的判断是正确的,但也有很多可以说的。
(在下面的内容中,我假设一个现代操作系统,它在x86体系结构上具有保护功能,线程完全通过任务切换实现。)
如果这是C代码,那么语法i += 1意味着i是某种整型变量,如果它是局部变量,则其值存储在诸如%eax之类的寄存器或堆栈中。首先处理简单的情况,如果i的值存储在一个寄存器中,比如%eax中,那么C编译器很可能会将该语句转换为如下内容:
当然,这是原子的,因为没有其他进程/线程能够修改正在运行的线程的%eax寄存器,并且在该指令完成之前,线程本身不能再次修改%eax。
如果i的值存储在堆栈中,那么这意味着存在内存提取、增量和提交。比如:
1 2 3
| movl -16(%esp), %eax
addl $1, %eax
movl %eax, -16(%esp) # this is the commit. It may actually come later if `i += 1` is part of a series of calculations involving `i`. |
号
通常,这一系列操作不是原子操作。但是,在现代操作系统上,进程/线程不应该能够修改另一个线程的堆栈,因此这些操作在没有其他进程能够干扰的情况下完成。因此,在这种情况下,i += 1语句也是原子的。
- 当然,我假设您的程序没有被调试程序调试,您的程序没有安全问题,允许攻击者破坏堆栈空间或注入机器代码,并且您的程序没有破坏自己的堆栈(例如,通过返回指向局部变量,其值随后由进程的另一个线程同时修改)。
不,C标准不能保证原子性,而且在实践中,操作不会是原子性的。您必须使用库(如Windows API)或编译器内置函数(gcc、msvc)。
只需在它周围放置一个互斥或信号灯。当然,它不是原子的,您可以用50个左右的线程来创建一个测试程序,访问同一个变量并递增它,以自己检查它。
C/C++语言本身并不要求原子性或缺少原子性。您需要依赖内部函数或库函数来确保原子行为。
- 两个C/C++语言!
- 或者更多,对于我们这些必须同时处理C89和C99的人。
- C++0x增加了原子性声明的一些操作,不是吗?
通常不会。
如果i是volatile,那么它将取决于您的CPU架构和编译器-如果在主内存中添加两个整数是CPU上的原子,那么C语句可能是volatile int i的原子。
- 不,即使使用volatile int,增量仍然由read、increment和store组成。cisc体系结构(如x86)通常将增量与加载或存储结合在一起,但整个序列不是原子的。在这种情况下,volatile没有帮助。
- 不一定-您可以有一个带有原子内存增量指令的CPU,但是一个C编译器仍然为一个易失性int输出三个指令(加载/增量/存储)。如果单个原子指令更快,那么这是编译器错过的优化,但afaik不是非法的。
- 我不这么认为。volatile只防止编译器执行优化,因为重用存储在寄存器中的保留内存值,该寄存器可能在外部发生了更改(例如,在内存中映射的I/O)。唯一的原子的ASM指令是test&set和compare&swap,编译器永远不会为该代码生成这些指令之一。
- @塞缪尔:显然你从来没有仔细研究过x86。它有一个前缀,可以使大多数指令原子化!
- 谁说这是关于x86的?
- @Novelocrat。你说得对,我已经有近十年没有学习过x86 ISA了(而且从来没有非常近距离地学习过)。我很好奇,那个"前缀"是什么?但是,真诚地说,如果任何广泛使用的C编译器自动为该C代码生成"前缀",我会非常惊讶。你知道吗?
- 我认为Novelocrat指的是锁前缀,尽管它只适用于有限的指令子集。但是,add是其中之一,add可以将内存操作数作为其目标,因此可以使用它以原子方式递增变量。
不,不是。如果i的值没有加载到某个寄存器中,就不能在单个汇编指令中完成。
- 是的,它可以——我在x86上做过这个。不过,编译器不能保证在这里发出原子指令。
- 在X86和X86 U64上,使用gcc(因此绝对不可移植):#define ATOMIC_ADD(x, y) asm volatile("lock add %1, %0" :"=m" (x) :"g" (y))