为什么GCC在变量初始化时不会检测溢出?

huangapple go评论66阅读模式
英文:

Why does GCC not detect overflow on variable initialization?

问题

以下是你要翻译的内容:

Why is it compiled without errors? What am I doing wrong?

#include <stdio.h>;

int main () {
    int n1 = 90, n2 = 93, n3 = 95;
    int i = 2147483647;
    int ii = 2147483646;
    int iii = 2147483650;
    char c1[50] = {'
#include <stdio.h>;

int main () {
    int n1 = 90, n2 = 93, n3 = 95;
    int i = 2147483647;
    int ii = 2147483646;
    int iii = 2147483650;
    char c1[50] = {'\0'};
    char c2[50] = {'\0'};
    char c3[50] = {'\0'};

    n1 = sprintf(c1, "%d", i+i);
    n2 = sprintf(c2, "%d", ii);
    n3 = sprintf(c3, "%d", iii);
    printf("n1 = %d, n2 = %d, n3 = %d\n  i = |%s| \n ii = |%s|\niii = |%s|\n", n1, n2, n3, c1, c2, c3);
    return 0;
}
'
};
char c2[50] = {'
#include <stdio.h>;

int main () {
    int n1 = 90, n2 = 93, n3 = 95;
    int i = 2147483647;
    int ii = 2147483646;
    int iii = 2147483650;
    char c1[50] = {'\0'};
    char c2[50] = {'\0'};
    char c3[50] = {'\0'};

    n1 = sprintf(c1, "%d", i+i);
    n2 = sprintf(c2, "%d", ii);
    n3 = sprintf(c3, "%d", iii);
    printf("n1 = %d, n2 = %d, n3 = %d\n  i = |%s| \n ii = |%s|\niii = |%s|\n", n1, n2, n3, c1, c2, c3);
    return 0;
}
'
};
char c3[50] = {'
#include <stdio.h>;

int main () {
    int n1 = 90, n2 = 93, n3 = 95;
    int i = 2147483647;
    int ii = 2147483646;
    int iii = 2147483650;
    char c1[50] = {'\0'};
    char c2[50] = {'\0'};
    char c3[50] = {'\0'};

    n1 = sprintf(c1, "%d", i+i);
    n2 = sprintf(c2, "%d", ii);
    n3 = sprintf(c3, "%d", iii);
    printf("n1 = %d, n2 = %d, n3 = %d\n  i = |%s| \n ii = |%s|\niii = |%s|\n", n1, n2, n3, c1, c2, c3);
    return 0;
}
'
};
n1 = sprintf(c1, "%d", i+i); n2 = sprintf(c2, "%d", ii); n3 = sprintf(c3, "%d", iii); printf("n1 = %d, n2 = %d, n3 = %d\n i = |%s| \n ii = |%s|\niii = |%s|\n", n1, n2, n3, c1, c2, c3); return 0; }
gcc filename -Wall -Wextra -Werror

I guess %d can't be more than int, but it's compiled and as a result:

n1 = 2, n2 = 10, n3 = 11

  i = |-2|

 ii = |2147483646|

iii = |-2147483646|

I was expecting a GCC error.

英文:

Why is it compiled without errors? What am I doing wrong?

#include &lt;stdio.h&gt;

int main (){
    int n1 = 90, n2 = 93, n3 = 95;
    int i = 2147483647;
    int ii = 2147483646;
    int iii = 2147483650;
    char c1[50] = {&#39;
#include &lt;stdio.h&gt;
int main (){
int n1 = 90, n2 = 93, n3 = 95;
int i = 2147483647;
int ii = 2147483646;
int iii = 2147483650;
char c1[50] = {&#39;\0&#39;};
char c2[50] = {&#39;\0&#39;};
char c3[50] = {&#39;\0&#39;};
n1 = sprintf(c1, &quot;%d&quot;, i+i);
n2 = sprintf(c2, &quot;%d&quot;, ii);
n3 = sprintf(c3, &quot;%d&quot;, iii);
printf(&quot;n1 = %d, n2 = %d, n3 = %d\n  i = |%s| \n ii = |%s|\niii = |%s|\n&quot;, n1, n2, n3, c1, c2, c3);
return 0;
}
&#39;}; char c2[50] = {&#39;
#include &lt;stdio.h&gt;
int main (){
int n1 = 90, n2 = 93, n3 = 95;
int i = 2147483647;
int ii = 2147483646;
int iii = 2147483650;
char c1[50] = {&#39;\0&#39;};
char c2[50] = {&#39;\0&#39;};
char c3[50] = {&#39;\0&#39;};
n1 = sprintf(c1, &quot;%d&quot;, i+i);
n2 = sprintf(c2, &quot;%d&quot;, ii);
n3 = sprintf(c3, &quot;%d&quot;, iii);
printf(&quot;n1 = %d, n2 = %d, n3 = %d\n  i = |%s| \n ii = |%s|\niii = |%s|\n&quot;, n1, n2, n3, c1, c2, c3);
return 0;
}
&#39;}; char c3[50] = {&#39;
#include &lt;stdio.h&gt;
int main (){
int n1 = 90, n2 = 93, n3 = 95;
int i = 2147483647;
int ii = 2147483646;
int iii = 2147483650;
char c1[50] = {&#39;\0&#39;};
char c2[50] = {&#39;\0&#39;};
char c3[50] = {&#39;\0&#39;};
n1 = sprintf(c1, &quot;%d&quot;, i+i);
n2 = sprintf(c2, &quot;%d&quot;, ii);
n3 = sprintf(c3, &quot;%d&quot;, iii);
printf(&quot;n1 = %d, n2 = %d, n3 = %d\n  i = |%s| \n ii = |%s|\niii = |%s|\n&quot;, n1, n2, n3, c1, c2, c3);
return 0;
}
&#39;}; n1 = sprintf(c1, &quot;%d&quot;, i+i); n2 = sprintf(c2, &quot;%d&quot;, ii); n3 = sprintf(c3, &quot;%d&quot;, iii); printf(&quot;n1 = %d, n2 = %d, n3 = %d\n i = |%s| \n ii = |%s|\niii = |%s|\n&quot;, n1, n2, n3, c1, c2, c3); return 0; }
gcc filename -Wall -Wextra -Werror

I guess %d can't be more than int, but it's compiled and as a result:

n1 = 2, n2 = 10, n3 = 11

  i = |-2|

 ii = |2147483646|

iii = |-2147483646|

I was expecting a GCC error.

答案1

得分: 12

在你初始化iii时存在一个错误,提供的常量不适合int

如果启用-pedantic,GCC将诊断此问题。根据文档

-Wpedantic
-pedantic
发出严格的ISO C和ISO C++要求的所有警告;拒绝使用禁止的扩展和一些不遵循ISO C和ISO C++的程序。对于ISO C,遵循任何使用的-std选项指定的ISO C标准版本。

这样做时,我会得到以下错误:

.code.tio.c: 在函数‘main’中:
.code.tio.c:7:15: 错误:从‘long int’到‘int’的转换中溢出,将值从‘2147483650’更改为‘-2147483646’ [-Werror=overflow]
     int iii = 2147483650;
               ^~~~~~~~~~
cc1:正在将所有警告视为错误处理

在线尝试!


其他问题

导致有符号整数溢出的算术运算

算术操作i+1触发了有符号整数溢出,这具有未定义的行为,因此编译器可以对该代码执行任何操作。

请注意,+运算符的两个操作数都具有类型int,因此如果生成了任何结果,它将是一个int。然而,由于有符号整数溢出是未定义的,可能不会生成任何结果(例如,程序可能会停止运行),或者可能会生成随机结果,或者可能会发生与您观察到的情况相匹配的其他溢出行为。

在一般情况下,编译器无法知道任何特定操作是否实际引起溢出。在这种情况下,静态代码分析可能已经揭示了它。我相信GCC执行了一些基本的静态代码分析,但它不需要识别每个未定义行为的实例。

使用sprintf而不是snprintf

虽然在你的特定上下文中使用sprintf是安全的,但通常更喜欢使用snprintf以防止缓冲区溢出漏洞。snprintf只需要一个额外的参数来指示缓冲区的大小,并会为你终止字符串。

英文:

There is an error in your initialization to iii, where the constant provided does not fit in int.

GCC will diagnose this issue if you enable -pedantic. From the documentation:

> -Wpedantic
-pedantic
Issue all the warnings demanded by strict ISO C and ISO C++; reject all programs that use forbidden extensions, and some other programs that do not follow ISO C and ISO C++. For ISO C, follows the version of the ISO C standard specified by any -std option used.

When doing so, I get the error:

.code.tio.c: In function ‘main’:
.code.tio.c:7:15: error: overflow in conversion from ‘long int’ to ‘int’ changes value from ‘2147483650’ to ‘-2147483646’ [-Werror=overflow]
     int iii = 2147483650;
               ^~~~~~~~~~
cc1: all warnings being treated as errors

Try it online!


Other problems

Arithmetic leading to signed integer overflow

The arithmetic operation i+1 triggers signed integer overflow, which has undefined behavior, so the compiler is free to do whatever it wants with that code.

Note that both operands to the + operator have type int, so if any result is generated, it would be an int. However, since signed integer overflow is undefined, no result may be generated (e.g., the program could just halt), or a random result may be generated, or some other overflow behavior may occur that matches your observation.

In the general case, there isn't any way for the compiler to know if any particular operation will actually cause overflow. In this case, static code analysis may have revealed it. I believe GCC does perform some rudimentary static code analysis, but it is not required to identify every instance of undefined behavior.

Using sprintf instead of snprintf

While it is safe to use sprintf in your particular context, it is generally preferable to use snprintf to guard against buffer overflow exploits. snprintf simply needs an extra parameter to indicate the size of the buffer, and it will NUL terminate the string for you.

2: https://tio.run/##dZDNTsMwDMfvfQqrqNomWtQ07Qbi4zU4UA6R022WSjalmYS07dUJSdaVgIQv/ts/O3aMxQbR2htS2B9kB0@DkbS7274kCSkDH4IUzBfHBJz5hGLwDA9lDqrygjvBvWgepxJyccXqVX3Pl/Uqyv8CyxjEpCkvBLdCA7K3pnx39Dhry9k5JtW/hP8hAYXFh712A9dzZDmkmUxzoFtaXHrDh6aCaiqYOI85/@HXghGlYVImxxMFwYNoFYTjnLLhBC6ga9AqirR7VDHf7Rtz8Lv6dZCPc7zpzhy0Aneqs7VfuO7FZrDFvpNCGUJbvHZa77T3n0YL50XffwM "C (gcc) – Try It Online"
3: https://en.wikipedia.org/wiki/Static_program_analysis
4: https://en.cppreference.com/w/cpp/io/c/fprintf

答案2

得分: 3

以下是翻译好的部分:

"I can really see after jxh's excellent answer somebody would still be saying "but whyyy?""

Here's why:

#define HKEY_LOCAL_MACHINE ((HANDLE)0x80000001)

No, HANDLE isn't int anymore, but it was in 1994. Everybody and their brother depended on signed overflow just working at compile time. If you changed it you broke your platform headers. That didn&#39t happen until the big 64 bit port.

The ancient compilers simply didn&#39t check for constant out of range. They just parsed the constant with something analogous to strtol; the overflow was really a runtime overflow in the compiler itself; without code written to detect it it simply didn&#39t exist.

The static analysis didn&#39t see 0x80000001; it saw -bignum. This used to bite people when cross compiling to different bitnesses; sometimes compile time constants were just wrong. One by one all this stuff got cleaned up, but there were too many places that depended on no warning on overflow (because the last thing you want is warnings in the platform headers), so it was left as is.

英文:

I can really see after jxh's excellent answer somebody would still be saying "but whyyy?"

Here's why:

   typedef int HANDLE;
   #define HKEY_LOCAL_MACHINE ((HANDLE)0x80000001)

No, HANDLE isn't int anymore, but it was in 1994. Everybody and their brother depended on signed overflow just working at compile time. If you changed it you broke your platform headers. That didn't happen until the big 64 bit port.

The ancient compilers simply didn't check for constant out of range. They just parsed the constant with something analogous to strtol; the overflow was really a runtime overflow in the compiler itself; without code written to detect it it simply didn't exist.

The static analysis didn't see 0x80000001; it saw -bignum. This used to bite people when cross compiling to different bitnesses; sometimes compile time constants were just wrong. One by one all this stuff got cleaned up, but there were too many places that depended on no warning on overflow (because the last thing you want is warnings in the platform headers), so it was left as is.

huangapple
  • 本文由 发表于 2023年2月9日 00:15:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/75388661.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定