什么是教育工具能够证明人们在C / C ++中所做的不必要的假设?


121

我想为SO准备一些教育工具,该工具应帮助初学者(和中级)程序员认识和挑战C,C ++及其平台中的不必要假设。

例子:

  • “整数环绕”
  • “每个人都有ASCII”
  • “我可以将函数指针存储在void *中”

我认为一个小的测试程序可以在各种平台上运行,这些平台运行“合理”的假设,这些假设是根据我们在SO方面的经验,通常由许多没有经验/半经验的主流开发人员做出的,并记录了它们在不同机器上的破坏方式。

这样做的目的不是要证明做某事是“安全的”(这是不可能做到的,测试如果失败就只能证明任何事情),而是向即使是最不懂事的人也演示最不起眼的表情如果其他计算机具有未定义或实现定义的行为,则在另一台计算机上中断。

为此,我想问你:

  • 如何改善这个想法?
  • 哪些测试将是好的,它们应该是什么样?
  • 您是否可以在可以使用的平台上运行测试并发布结果,以便最终获得平台数据库,它们之间的差异以及为何允许这种差异?

这是测试玩具的当前版本:

#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <stddef.h>
int count=0;
int total=0;
void expect(const char *info, const char *expr)
{
    printf("..%s\n   but '%s' is false.\n",info,expr);
    fflush(stdout);
    count++;
}
#define EXPECT(INFO,EXPR) if (total++,!(EXPR)) expect(INFO,#EXPR)

/* stack check..How can I do this better? */
ptrdiff_t check_grow(int k, int *p)
{
    if (p==0) p=&k;
    if (k==0) return &k-p;
    else return check_grow(k-1,p);
}
#define BITS_PER_INT (sizeof(int)*CHAR_BIT)

int bits_per_int=BITS_PER_INT;
int int_max=INT_MAX;
int int_min=INT_MIN;

/* for 21 - left to right */
int ltr_result=0;
unsigned ltr_fun(int k)
{
    ltr_result=ltr_result*10+k;
    return 1;
}

int main()
{
    printf("We like to think that:\n");
    /* characters */
    EXPECT("00 we have ASCII",('A'==65));
    EXPECT("01 A-Z is in a block",('Z'-'A')+1==26);
    EXPECT("02 big letters come before small letters",('A'<'a'));
    EXPECT("03 a char is 8 bits",CHAR_BIT==8);
    EXPECT("04 a char is signed",CHAR_MIN==SCHAR_MIN);

    /* integers */
    EXPECT("05 int has the size of pointers",sizeof(int)==sizeof(void*));
    /* not true for Windows-64 */
    EXPECT("05a long has at least the size of pointers",sizeof(long)>=sizeof(void*));

    EXPECT("06 integers are 2-complement and wrap around",(int_max+1)==(int_min));
    EXPECT("07 integers are 2-complement and *always* wrap around",(INT_MAX+1)==(INT_MIN));
    EXPECT("08 overshifting is okay",(1<<bits_per_int)==0);
    EXPECT("09 overshifting is *always* okay",(1<<BITS_PER_INT)==0);
    {
        int t;
        EXPECT("09a minus shifts backwards",(t=-1,(15<<t)==7));
    }
    /* pointers */
    /* Suggested by jalf */
    EXPECT("10 void* can store function pointers",sizeof(void*)>=sizeof(void(*)()));
    /* execution */
    EXPECT("11 Detecting how the stack grows is easy",check_grow(5,0)!=0);
    EXPECT("12 the stack grows downwards",check_grow(5,0)<0);

    {
        int t;
        /* suggested by jk */
        EXPECT("13 The smallest bits always come first",(t=0x1234,0x34==*(char*)&t));
    }
    {
        /* Suggested by S.Lott */
        int a[2]={0,0};
        int i=0;
        EXPECT("14 i++ is strictly left to right",(i=0,a[i++]=i,a[0]==1));
    }
    {
        struct {
            char c;
            int i;
        } char_int;
        EXPECT("15 structs are packed",sizeof(char_int)==(sizeof(char)+sizeof(int)));
    }
    {
        EXPECT("16 malloc()=NULL means out of memory",(malloc(0)!=NULL));
    }

    /* suggested by David Thornley */
    EXPECT("17 size_t is unsigned int",sizeof(size_t)==sizeof(unsigned int));
    /* this is true for C99, but not for C90. */
    EXPECT("18 a%b has the same sign as a",((-10%3)==-1) && ((10%-3)==1));

    /* suggested by nos */
    EXPECT("19-1 char<short",sizeof(char)<sizeof(short));
    EXPECT("19-2 short<int",sizeof(short)<sizeof(int));
    EXPECT("19-3 int<long",sizeof(int)<sizeof(long));
    EXPECT("20 ptrdiff_t and size_t have the same size",(sizeof(ptrdiff_t)==sizeof(size_t)));
#if 0
    {
        /* suggested by R. */
        /* this crashed on TC 3.0++, compact. */
        char buf[10];
        EXPECT("21 You can use snprintf to append a string",
               (snprintf(buf,10,"OK"),snprintf(buf,10,"%s!!",buf),strcmp(buf,"OK!!")==0));
    }
#endif

    EXPECT("21 Evaluation is left to right",
           (ltr_fun(1)*ltr_fun(2)*ltr_fun(3)*ltr_fun(4),ltr_result==1234));

    {
    #ifdef __STDC_IEC_559__
    int STDC_IEC_559_is_defined=1;
    #else 
    /* This either means, there is no FP support
     *or* the compiler is not C99 enough to define  __STDC_IEC_559__
     *or* the FP support is not IEEE compliant. */
    int STDC_IEC_559_is_defined=0;
    #endif
    EXPECT("22 floating point is always IEEE",STDC_IEC_559_is_defined);
    }

    printf("From what I can say with my puny test cases, you are %d%% mainstream\n",100-(100*count)/total);
    return 0;
}

哦,我从一开始就制作了这个社区Wiki,因为我认为人们在阅读本文时想编辑我的笨拙。

更新感谢您的输入。我从您的答案中添加了一些案例,将看看是否可以像Greg建议的那样为此设置一个github。

更新:我为此创建了一个github存储库,文件为“ gotcha.c”:

请在此处回答补丁或新想法,以便在此处进行讨论或澄清。然后,我将它们合并到gotcha.c中。


7
考虑DOS中的介质模型。函数可以存储在多个段中,因此函数指针的长度为32位。但是您的数据仅存储在单个段中,因此数据指针只有16位长。由于void *是数据指针,因此它是16位宽,因此您不能将函数指针放在一个指针中。参见c-jump.com/CIS77/ASM/Directives/D77_0030_models.htm
大卫

6
也许您可以将此代码放在github.com上,然后人们可以轻松地贡献补丁。
格雷格·希吉尔

1
很多事情在这里应该有所帮助:stackoverflow.com/questions/367633/...
马丁纽约

4
POSIX要求函数指针具有与void *相同的表示形式,并且可以进行转换(使用强制转换)而不会丢失信息。原因之一是dlsym()返回void *,但同时用于数据和函数指针。因此依靠它可能并不坏。
jilles 2010年

3
@tristopia:第15点在这里,因为许多初学者常常惊讶地发现数据不是连续打包,而是与特定边界对齐。当他们更改会员顺序并获得不同的对象大小时,他们会感到困惑。另外,打包是许多现代微控制器或嵌入式设备的默认模式。我的AVR Atmega和TurboC / MSDOS输出也打包了。MSDOS仍在工业应用中使用。
Nordic Mainframe 2010年

Answers:


91

子表达式的评估顺序,包括

  • 函数调用的参数和
  • 运营商的操作数(例如,+-=*/),以下除外:
    • 二进制逻辑运算符(&&||),
    • 三元条件运算符(?:),以及
    • 逗号运算符(,

未指定

例如

  int Hello()
  {
       return printf("Hello"); /* printf() returns the number of 
                                  characters successfully printed by it
                               */
  }

  int World()
  {
       return printf("World !");
  }

  int main()
  {

      int a = Hello() + World(); //might print Hello World! or World! Hello
      /**             ^
                      | 
                Functions can be called in either order
      **/
      return 0;
  } 

1
我一直都知道函数参数,但是我从来没有从运算符的角度考虑它……如果我看到你在生产环境中编写这样的代码,我会给你一个湿面。
riwalk

3
@Billy:但仅适用于操作员的原始版本。
丹尼斯·齐克福斯

1
@丹尼斯:是的。(这就是为什么它是有效的/更有效的C ++中的一个永远不会使它们超载的原因(除非您正在撰写boost::spirit
Billy ONeal 2010年

1
@Daniel:我不确定你想说什么。听起来您似乎建议重载运算符是可以的,因为只有类的用户可能会弄​​错它,并且如果您不是用直接的C ++编写,也没关系。两者都没有任何意义。
丹尼斯·齐克福斯

2
@ user420536:行为只是未指定但未定义。是的,该示例可以打印Hello World!还是世界!您好,但这只是未指定,因为运算+符的操作数的求值顺序未指定(编译器编写人员无需记录行为)。它不违反任何顺序点规则。
Prasoon Saurav'2

38

sdcc 29.7 / ucSim / Z80

We like to think that:
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..19-2 short<int
   but 'sizeof(short)<sizeof(int)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
..25 pointer arithmetic works outside arrays
   but '(diff=&var.int2-&var.int1, &var.int1+diff==&var.int2)' is false.
From what I can say with my puny test cases, you are Stop at 0x0013f3: (106) Invalid instruction 0x00dd

printf崩溃。“ O_O”


gcc 4.4@x86_64-suse-linux

We like to think that:
..05 int has the size of pointers
but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..14 i++ is strictly left to right
but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..26 sizeof() does not evaluate its arguments
but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
From what I can say with my puny test cases, you are 79% mainstream

gcc 4.4@x86_64-suse-linux(-O2)

We like to think that:
..05 int has the size of pointers
but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
but '(1<<bits_per_int)==0' is false.
..14 i++ is strictly left to right
but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..26 sizeof() does not evaluate its arguments
but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
From what I can say with my puny test cases, you are 82% mainstream

铛2.7@x86_64-suse-linux

We like to think that:
..05 int has the size of pointers
but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..14 i++ is strictly left to right
but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..21a Function Arguments are evaluated right to left
but '(gobble_args(0,ltr_fun(1),ltr_fun(2),ltr_fun(3),ltr_fun(4)),ltr_result==4321)' is false.
ltr_result is 1234 in this case
..25a pointer arithmetic works outside arrays
but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
..26 sizeof() does not evaluate its arguments
but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
From what I can say with my puny test cases, you are 72% mainstream

open64 4.2.3@x86_64-suse-linux

We like to think that:
..05 int has the size of pointers
but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..15 structs are packed
but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..21a Function Arguments are evaluated right to left
but '(gobble_args(0,ltr_fun(1),ltr_fun(2),ltr_fun(3),ltr_fun(4)),ltr_result==4321)' is false.
ltr_result is 1234 in this case
..25a pointer arithmetic works outside arrays
but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
..26 sizeof() does not evaluate its arguments
but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
From what I can say with my puny test cases, you are 75% mainstream

英特尔11.1@x86_64-suse-linux

We like to think that:
..05 int has the size of pointers
but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..14 i++ is strictly left to right
but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..21a Function Arguments are evaluated right to left
but '(gobble_args(0,ltr_fun(1),ltr_fun(2),ltr_fun(3),ltr_fun(4)),ltr_result==4321)' is false.
ltr_result is 1234 in this case
..26 sizeof() does not evaluate its arguments
but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
From what I can say with my puny test cases, you are 75% mainstream

Turbo C ++ / DOS /小内存

We like to think that:
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..16 malloc()=NULL means out of memory
but '(malloc(0)!=NULL)' is false.
..19-2 short<int
but 'sizeof(short)<sizeof(int)' is false.
..22 floating point is always IEEE
but 'STDC_IEC_559_is_defined' is false.
..25 pointer arithmetic works outside arrays
but '(diff=&var.int2-&var.int1, &var.int1+diff==&var.int2)' is false.
..25a pointer arithmetic works outside arrays
but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
From what I can say with my puny test cases, you are 81% mainstream

Turbo C ++ / DOS /中型内存

We like to think that:
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..10 void* can store function pointers
but 'sizeof(void*)>=sizeof(void(*)())' is false.
..16 malloc()=NULL means out of memory
but '(malloc(0)!=NULL)' is false.
..19-2 short<int
but 'sizeof(short)<sizeof(int)' is false.
..22 floating point is always IEEE
but 'STDC_IEC_559_is_defined' is false.
..25 pointer arithmetic works outside arrays
but '(diff=&var.int2-&var.int1, &var.int1+diff==&var.int2)' is false.
..25a pointer arithmetic works outside arrays
but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
From what I can say with my puny test cases, you are 78% mainstream

Turbo C ++ / DOS /紧凑型内存

We like to think that:
..05 int has the size of pointers
but 'sizeof(int)==sizeof(void*)' is false.
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..16 malloc()=NULL means out of memory
but '(malloc(0)!=NULL)' is false.
..19-2 short<int
but 'sizeof(short)<sizeof(int)' is false.
..20 ptrdiff_t and size_t have the same size
but '(sizeof(ptrdiff_t)==sizeof(size_t))' is false.
..22 floating point is always IEEE
but 'STDC_IEC_559_is_defined' is false.
..25 pointer arithmetic works outside arrays
but '(diff=&var.int2-&var.int1, &var.int1+diff==&var.int2)' is false.
..25a pointer arithmetic works outside arrays
but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
From what I can say with my puny test cases, you are 75% mainstream

cl65 @ Commodore PET(辅助仿真器)

替代文字


我将在以后更新这些:


Windows XP上的Borland C ++ Builder 6.0

..04 a char is signed
   but 'CHAR_MIN==SCHAR_MIN' is false.
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09 overshifting is *always* okay
   but '(1<<BITS_PER_INT)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..16 malloc()=NULL means out of memory
   but '(malloc(0)!=NULL)' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 71% mainstream

Visual Studio Express 2010 C ++ CLR,Windows 7 64位

(必须编译为C ++,因为CLR编译器不支持纯C语言)

We like to think that:
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..14 i++ is structly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 78% mainstream

MINGW64(gcc-4.5.2预发布)

- http://mingw-w64.sourceforge.net/

We like to think that:
..05 int has the size of pointers
   but 'sizeof(int)==sizeof(void*)' is false.
..05a long has at least the size of pointers
   but 'sizeof(long)>=sizeof(void*)' is false.
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..14 i++ is structly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
   but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 67% mainstream

64位Windows使用LLP64模型:intlong都定义为32位,这意味着两个指针的长度都不足够。


avr-gcc 4.3.2 / ATmega168(Arduino Diecimila)

失败的假设是:

..14 i++ is structly left to right
..16 malloc()=NULL means out of memory
..19-2 short<int
..21 Evaluation is left to right
..22 floating point is always IEEE

Atmega168具有16位PC,但代码和数据位于单独的地址空间中。较大的Atmegas具有22位PC!


MacOSX 10.6上的gcc 4.2.1,使用-arch ppc编译

We like to think that:
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..13 The smallest bits come always first
   but '(t=0x1234,0x34==*(char*)&t)' is false.
..14 i++ is structly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 78% mainstream


32
您还确定了另一个假设:您可以在终端行上容纳80个字符。
Mike Seymour,2010年

3
sizeof(void*)>=sizeof(void(*)())比==更相关。我们关心的只是“是否可以将函数指针存储在void指针中”,因此您需要测试的假设是a void*是否至少与函数指针一样大。
jalf

1
如果您的环境符合POSIX,则可以使用sizeof(void*)>=sizeof(void(*)())-请参阅opengroup.org/onlinepubs/009695399/functions/dlsym.html
Daniel Earwicker 2010年

26

很久以前,我从一本教科书

printf("sizeof(int)=%d\n", sizeof(int));

作为示例问题。它失败的学生,因为sizeof类型的产量值size_t,不是intint在这个实现为16位,size_t为32,这是大端。(该平台是基于680x0的Macintoshes上的LightspeedC。我说它是很久以前的。)


7
+1指出此类最常见且最常被忽略的错误。
R .. GitHub停止帮助ICE,2010年

4
在64位系统上也会发生这种情况,其中size_t为64位,而int几乎总是较短。Win64仍然很奇怪,因为size_t在unsigned long long那。加入测试17
北欧大型机

不幸的是,Microsoft的C运行时不支持大小整数的z修饰符size_t,并且long long在某些平台上也不支持。因此,没有安全的便携式方法来格式化或转换对象的打印尺寸。
菲尔·米勒

15

您需要包括人们所做的++--假设。

a[i++]= i;

例如,从语法上讲是合法的,但根据要推理的事物太多而产生不同的结果。

任何具有++(或--)和变量多次出现的语句都是一个问题。


这也是一个常见的问题!
Matthieu M.

8

很有意思!

我想到的其他内容可能对检查有用:

  • 函数指针和数据指针是否存在于同一地址空间中?(在DOS小型模式之类的哈佛体系结构计算机中出现了缺陷。不过,您不知道如何进行测试。)

  • 如果您采用NULL数据指针并将其转换为适当的整数类型,那么它的数值是否为0?(在某些真正古老的机器上有缺陷-参见http://c-faq.com/null/machexamp.html。)具有函数指针的同上。同样,它们可以是不同的值。

  • 指针越过其相应存储对象的末尾,然后再次返回,是否会导致有意义的结果?(我不知道这实际上会中断任何机器,但是我相信C规范甚至不允许您考虑不指向(a)数组内容或(b)元素的指针)的指针。紧接在数组之后或(c)为NULL。请参见http://c-faq.com/aryptr/non0based.html。)

  • 用<和>比较两个指向不同存储对象的指针会产生一致的结果吗?(我可以想象在基于奇异的基于段的计算机上会出现这种情况;规范禁止进行此类比较,因此编译器将只有权比较指针的偏移量部分,而不是段部分。)

嗯 我会尝试考虑更多。

编辑:添加了一些澄清链接到优秀的C常见问题解答。


2
偶然地,前一段时间我做了一个名为Clue(cluecc.sourceforge.net)的实验项目,该项目使您可以将C编译为Lua,Javascript,Perl,LISP等。它无情地利用了C标准中未定义的行为来使指针起作用。尝试对此进行测试可能会很有趣。
大卫

1
IIRC C允许您将指针从对象末尾增加1,但不能再增加。但是,不允许将其减小到对象开始之前的位置。
R .. GitHub停止帮助ICE,2010年

@R。在C ++中也一样。如果在不仅仅将指针视为整数的CPU上递增指针导致溢出,则进一步递增可能会中断。
jalf

5

我认为您应该努力区分两种非常不同的“错误”假设。好的一半(右移和符号扩展,与ASCII兼容的编码,内存是线性的,与数据和函数指针兼容的等等)对于大多数 C编码人员而言都是相当合理的假设,甚至可能包含在标准中如果C是在今天设计的,以及我们是否没有继承IBM的旧版本。另一半(与内存别名有关的事情,输入和输出内存重叠时库函数的行为,32位假设(例如适合指针int或可以使用的指针)malloc 如果没有原型,则调用约定对于可变函数和非可变函数是相同的,...)与现代编译器想要执行的优化冲突或与移植到64位计算机或其他新技术上。


不只是“ IBM垃圾”(尽管我同意IBM的东西是垃圾)。今天,许多嵌入式系统都存在类似的问题。
rmeador

要澄清的是,malloc不使用原型就意味着不包括<stdlib.h>,如果要支持64位malloc,则默认为int malloc(int),这是不可以。
乔伊·亚当斯

从技术上讲<stdlib.h>,只要包含另一个定义的标头,size_t然后malloc自己声明正确的原型,就可以自由地不包含它。
R .. GitHub停止帮助ICE,2010年

5

这是一个有趣的事情:此功能有什么问题?

float sum(unsigned int n, ...)
{
    float v = 0;
    va_list ap;
    va_start(ap, n);
    while (n--)
        v += va_arg(ap, float);
    va_end(ap);
    return v;
}

[答案(rot13):Inevnqvp nethzragf borl gur byq X&E cebzbgvba ehyrf,juvpu zrnaf lbh pnaabg hfr'sybng'('pune'be'fubeg')va in_net!Naq gur pbzcvyre vf erdhverq abg gb gerng guvf nf n pbzcvyr-gvzr reebe。(TPP qbrf rzvg n jneavat,gubhtu。)]


哦,那是一个好人。clang 2.7会吃掉它,并在没有警告的情况下完全胡说八道。
北欧大型机,2010年

va_arg如果是宏,则进行扩展,而while循环仅执行第一条语句,也许很多?
Maister

否(如果发生这种情况,则可能是实现中的错误)。
zwol10年

5
EXPECT("## pow() gives exact results for integer arguments", pow(2, 4) == 16);

另一个是关于中的文本模式fopen。大多数程序员都假定文本和二进制文件是相同的(Unix)或文本模式添加了\r字符(Windows)。但是C已被移植到使用固定宽度记录的系统,在该记录上,fputc('\n', file)文本文件意味着添加空格或其他内容,直到文件大小为记录长度的倍数为止。

这是我的结果:

x86-64上的gcc(Ubuntu 4.4.3-4ubuntu5)4.4.3

We like to think that:
..05 int has the size of pointers
   but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..14 i++ is strictly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
   but 'sizeof(size_t)==sizeof(unsigned int)' is false.
From what I can say with my puny test cases, you are 78% mainstream

我实际上已经看到了pow(2, n)与位操作结合在一起的代码。
10年

4

其中一些无法从C内部轻松测试,因为该程序可能会在假设不成立的实现上崩溃。


“用指针值的变量做任何事情都是可以的。如果您取消引用它,则只需要包含一个有效的指针值即可。”

void noop(void *p); /* A no-op function that the compiler doesn't know to optimize away */
int main () {
    char *p = malloc(1);
    free(p);
    noop(p); /* may crash in implementations that verify pointer accesses */
    noop(p - 42000); /* and if not the previous instruction, maybe this one */
}

与整数和浮点类型(除外unsigned char)相同,允许使用陷阱表示形式。


“整数计算会回绕。因此该程序会打印一个大的负整数。”

#include <stdio.h>
int main () {
    printf("%d\n", INT_MAX+1); /* may crash due to signed integer overflow */
    return 0;
}

(仅C89。)“可以从。的结尾掉下来main。”

#include <stdio.h>
int main () {
    puts("Hello.");
} /* The status code is 7 on many implementations. */

2
举一个具体的例子:使用编译时gcc -ftrapv -O,输出We like to think that:之后是Aborted
caf 2010年

@caf:“此选项在加,减,乘运算上生成有符号溢出的陷阱。” 很高兴知道,谢谢。
吉尔(Gilles)'所以

1
最后一个是在C OK ++(98,03和0X)为好,并隐式返回0
jalf

这很讨厌,因为pre-ANSI C允许这样做,而C99也允许。
约书亚

@Joshua:AFAIK ANSI C之前的版本和C89在main从无值返回时没有区别:程序正确,但是返回了未定义的终止状态(C89§2.1.2.2)。使用许多实现(例如gcc和较旧的unix编译器),您将获得那时某个寄存器中的内容。该程序通常可以正常工作,直到用于生成文件或其他检查终止状态的环境中为止。
吉尔斯(Gilles)'所以

4

好吧,经典的可移植性假设尚未得到解释

  • 关于整数类型大小的假设
  • 字节序

4
“字节序”,包括“有字节序”:存在中字节序的机器,并且该标准允许诸如将short值fedcab9876543210(即16个二进制数字)存储为两个字节0248ace和fdb97531之类的怪异事物。
吉尔斯(Gilles)'所以

是的,字节序肯定包括混合/中间字节序以及大小字节。如果您使用自定义硬件,则可以在任何公共汽车上享受自己喜欢的生活。
jk。

中间字节序称为PDP字节序。Gilles甚至描述了一些怪异的东西,尽管这会给实施TCP / IP带来麻烦。
约书亚

@吉尔斯:中端人...我很高兴我没有在那个基础上发展。(但是我现在肯定会要求我做一个中端网络项目)...
Paul Nathan 2010年

ARM FPE使用中端字节双精度字,其中将它们存储为<high quad> <low quad>对,但是每个Quad内部的位顺序是错误的。(非常感谢,ARM VFP不再这样做了。)
David

4
  • 由于浮点表示而导致的离散化错误。例如,如果您使用标准公式来求解二次方程式,或者使用有限差分来近似导数,或者使用标准公式来计算方差,则由于计算相似数字之间的差异会导致精度损失。解决线性系统的Gauß算法不好,因为舍入误差会累积,因此使用QR或LU分解,Cholesky分解,SVD等。浮点数的加法不具有关联性。有反常,无限和NaN值。a + bab

  • 字符串:字符,代码点和代码单位之间的差异。如何在各种操作系统上实现Unicode;Unicode编码。C ++无法以可移植的方式打开具有任意Unicode文件名的文件。

  • 争用条件,即使没有线程:如果您测试文件是否存在,结果可能随时变为无效。

  • ERROR_SUCCESS = 0


4

包括检查整数大小。大多数人认为int大于short大于char。但是,这些都可能是错误的:sizeof(char) < sizeof(int); sizeof(short) < sizeof(int); sizeof(char) < sizeof(short)

这段代码可能会失败(崩溃导致无法访问)

unsigned char buf[64];

int i = 234;
int *p = &buf[1];
*p = i;
i = *p;

这段代码在C ++中会失败吗?IIRC,在不相关类型之间转换指针是非法的,除了char *,可以将其转换为任何类型(或者反之亦然?)。
rmeador

1
您可以int *p = (int*)&buf[1];使用C ++ 来做,人们希望它也能起作用。

@nos,是的,可能会失败,但是失败是崩溃,因此他的程序无法测试那个。:(
约书亚

1
sizeof(char) < sizeof(int)是必须的。例如,fgetc()以转换为int的无符号char形式返回字符的值,或者EOF它是负值。unsigned char可能没有填充位,因此唯一的方法是使int大于char。另外,(大多数版本的)C规范要求-32767..32767范围内的任何值都可以存储在int中。
jilles 2010年

还是@illes,那里有32位字符和32位整数的DSP。

3

关于内置数据类型的两件事:

  • charsigned char实际上是两种不同的类型(与intsigned int分别引用相同的有符号整数类型不同)。
  • 带符号的整数不需要使用二进制补码。一个人的补码和正负号也是负数的有效表示。这使得涉及负数的位操作实现定义为
  • 如果将超出范围的整数分配给有符号的整数变量,则行为是实现定义的
  • 在C90中,-3/5可以返回0-1。仅当C99向上和C ++ 0x向上时,才能保证在一个操作数为负的情况下向零舍入。
  • 内置类型没有确切的尺寸保证。该标准仅覆盖最低要求,如int具有至少 16位,一个long具有至少 32位,一个long long具有至少 64位。A float至少可以正确表示6个最高有效十进制数字。A double至少可以正确表示10个最高有效的十进制数字。
  • IEEE 754对于表示浮点数不是强制性的。

诚然,在大多数机器上,我们都有二进制补码和IEEE 754浮点数。


我想知道超出范围的整数分配是实现定义而不是未定义行为的什么价值?在某些平台上,这样的要求将迫使编译器生成额外的代码,int mult(int a,int b) { return (long)a*b;}例如[如果int是32位,但是寄存器and long为64]。没有这样的要求,最快的实现的“自然”行为long l=mult(1000000,1000000);将设置l1000000000000,即使这对于而言是“不可能的”值int
2014年

3

这个怎么样:

任何数据指针都不能与有效的函数指针相同。

对于所有平面模型,MS-DOS TINY,LARGE和HUGE模型均为TRUE,对于MS-DOS SMALL模型为false,对于MEDIUM和COMPACT模型几乎始终为false(取决于加载地址,您将需要一个非常旧的DOS才能使其成为现实)。

我无法为此编写测试

更糟糕的是:可以比较转换为ptrdiff_t的指针。这对于MS-DOS LARGE模型不是正确的(LARGE和HUGE之间的唯一区别是HUGE添加了编译器代码以规范化指针)。

我无法编写测试,因为这种炸弹袭击的环境不会分配大于64K的缓冲区,因此演示该代码的代码将在其他平台上崩溃。

此特定测试将在一个现已失效的系统上通过(注意,这取决于malloc的内部):

  char *ptr1 = malloc(16);
  char *ptr2 = malloc(16);
  if ((ptrdiff_t)ptr2 - 0x20000 == (ptrdiff_t)ptr1)
      printf("We like to think that unrelated pointers are equality comparable when cast to the appropriate integer, but they're not.");

3

编辑:更新到程序的最新版本

Solaris-SPARC

gcc 3.4.6 in 32位

We like to think that:
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09 overshifting is *always* okay
   but '(1<<BITS_PER_INT)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..13 The smallest bits always come first
   but '(t=0x1234,0x34==*(char*)&t)' is false.
..14 i++ is strictly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 72% mainstream

gcc 3.4.6 in 64位

We like to think that:
..05 int has the size of pointers
   but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09 overshifting is *always* okay
   but '(1<<BITS_PER_INT)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..13 The smallest bits always come first
   but '(t=0x1234,0x34==*(char*)&t)' is false.
..14 i++ is strictly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
   but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 68% mainstream

并使用SUNStudio 11 32位

We like to think that:
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..13 The smallest bits always come first
   but '(t=0x1234,0x34==*(char*)&t)' is false.
..14 i++ is strictly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
From what I can say with my puny test cases, you are 79% mainstream

并使用SUNStudio 11 64位

We like to think that:
..05 int has the size of pointers
   but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..13 The smallest bits always come first
   but '(t=0x1234,0x34==*(char*)&t)' is false.
..14 i++ is strictly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
   but 'sizeof(size_t)==sizeof(unsigned int)' is false.
From what I can say with my puny test cases, you are 75% mainstream

2

您可以使用文本模式(fopen("filename", "r"))读取任何类型的文本文件。

尽管从理论上讲这应该可以正常工作,但是如果您还在ftell()代码中使用它,并且您的文本文件具有UNIX样式的行尾,那么在Windows标准库的某些版本中,该文件ftell()通常会返回无效值。解决方案是改用二进制模式(fopen("filename", "rb"))。


1

AIX 5.3上的gcc 3.3.2(是的,我们需要更新gcc)

We like to think that:
..04 a char is signed
   but 'CHAR_MIN==SCHAR_MIN' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..13 The smallest bits come always first
   but '(t=0x1234,0x34==*(char*)&t)' is false.
..14 i++ is structly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..16 malloc()=NULL means out of memory
   but '(malloc(0)!=NULL)' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 71% mainstream

1

有人可能会在C ++中做一个假设,即a struct限于它在C中可以做的事情。事实是,在C ++中,a struct类似于a,class只是默认情况下它具有所有公共属性。

C ++结构:

struct Foo
{
  int number1_;  //this is public by default


//this is valid in C++:    
private: 
  void Testing1();
  int number2_;

protected:
  void Testing2();
};

1

不同系统上的标准数学函数不会给出相同的结果。


1

32位x86上的Visual Studio Express 2010。

Z:\sandbox>cl testtoy.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

testtoy.c
testtoy.c(54) : warning C4293: '<<' : shift count negative or too big, undefined
 behavior
Microsoft (R) Incremental Linker Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:testtoy.exe
testtoy.obj

Z:\sandbox>testtoy.exe
We like to think that:
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..14 i++ is structly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 78% mainstream

1

通过Codepad.orgC++: g++ 4.1.2 flags: -O -std=c++98 -pedantic-errors -Wfatal-errors -Werror -Wall -Wextra -Wno-missing-field-initializers -Wwrite-strings -Wno-deprecated -Wno-unused -Wno-non-virtual-dtor -Wno-variadic-macros -fmessage-length=0 -ftemplate-depth-128 -fno-merge-constants -fno-nonansi-builtins -fno-gnu-keywords -fno-elide-constructors -fstrict-aliasing -fstack-protector-all -Winvalid-pch)。

请注意,Codepad没有stddef.h。由于使用警告作为错误的键盘,我删除了测试9。我也重命名了该count变量,因为由于某种原因已经定义了该变量。

We like to think that:
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..14 i++ is structly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
From what I can say with my puny test cases, you are 84% mainstream

1

标准过多允许右移,或者值得测试吗?

标准C是否指定以下程序的行为:

无效print_string(char * st)
{
  char ch;
  while((ch = * st ++)!= 0)
    推杆(ch); / *假设已定义* /
}
int main(无效)
{
  print_string(“ Hello”);
  返回0;
}

在我使用的至少一个编译器上,除非print_string的参数为“ char const *”,否则该代码将失败。标准是否允许这样的限制?

有些系统允许一个产生指向未对齐的“ int”的指针,而另一些则不允许。可能值得测试。


C89§3.3.7:“如果右操作数的值为负或大于或等于提升后的左操作数的位宽度,则行为不确定。” (同时适用于<<>>)。C99在§6.5.7-3中具有相同的语言。
吉尔(Gilles)“所以,别再邪恶了”,2010年

除了putch(为什么不使用标准putchar?),我在程序中看不到任何未定义的行为。C89§3.1.4指定“字符串文字具有[…]类型的'char数组'”(注意:no const),并且“如果程序试图修改字符串文字[…],则行为是不确定的” 。那是什么编译器,它如何翻译该程序?
吉尔斯(Gilles)'所以

2
在C ++中,字符常量不是 char [],而是 const char []。但是... 类型系统中曾经有一个特定的漏洞,可让您在预期使用char *且不会出现类型错误的上下文中使用字符串常量。这导致print_string(“ foo”)可以工作,而print_string(“ foo” +0)不能工作的情况。这非常令人困惑,尤其是在默认情况下使用C ++编译器编译C文件的环境中。漏洞已在新的编译器中消除,但仍然有很多旧的编译器。AFAIK C99仍将字符串常量定义为char []。
大卫

1
在适用于Microchip PIC系列控制器的HiTech编译器上,没有存储限定符的指针只能指向RAM。const限定的指针可以指向RAM或ROM。非const限定的指针直接在代码中取消引用;const限定的指针通过库例程取消引用。取决于PIC的特定类型,非const限定的指针为1或2个字节。const限定的值为2或3。由于ROM比RAM丰富得多,因此在ROM中具有常量通常是一件好事。
supercat

@David Given:也请注意我之前的评论。我更喜欢使用除“ const”以外的限定词来表示硬件存储类的编译器。HiTech编译器在存储类别分配方面存在一些令人讨厌的怪癖(例如,“组件大小”为一个字节的数据项或超过256个字节的数据项在“大”段中。其他数据项在“ BSS”段为他们定义的模块;所有的‘模块中的BSS’项目必须符合超过256个字节是稍短的256个字节数组可以是一个真正的滋扰。
supercat

0

仅供参考,对于那些必须将C技能转换为Java的人,这里有一些陷阱。

EXPECT("03 a char is 8 bits",CHAR_BIT==8);
EXPECT("04 a char is signed",CHAR_MIN==SCHAR_MIN);

在Java中,char是16位并有符号。字节为8位并有符号。

/* not true for Windows-64 */
EXPECT("05a long has at least the size of pointers",sizeof(long)>=sizeof(void*));

long始终是64位,引用可以是32位或64位(如果您拥有的应用程序超过32 GB),则64位JVM通常使用32位引用。

EXPECT("08 overshifting is okay",(1<<bits_per_int)==0);
EXPECT("09 overshifting is *always* okay",(1<<BITS_PER_INT)==0);

屏蔽了该移位,以便i << 64 == i == i << -64,i << 63 == i << -1

EXPECT("13 The smallest bits always come first",(t=0x1234,0x34==*(char*)&t));

ByteOrder.nativeOrder()可以为BIG_ENDIAN或LITTLE_ENDIAN

EXPECT("14 i++ is strictly left to right",(i=0,a[i++]=i,a[0]==1));

i = i++ 永不改变 i

/* suggested by David Thornley */
EXPECT("17 size_t is unsigned int",sizeof(size_t)==sizeof(unsigned int));

无论JVM是32位还是64位,集合和数组的大小始终为32位。

EXPECT("19-1 char<short",sizeof(char)<sizeof(short));
EXPECT("19-2 short<int",sizeof(short)<sizeof(int));
EXPECT("19-3 int<long",sizeof(int)<sizeof(long));

char是16位,short是16位,int是32位,long是64位。

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.