Tag: 语言律师

使用gets 防止缓冲区溢出

gets的声明是: char * gets ( char * str ); 请注意str的最大尺寸的明显遗漏 。 cplusplus.com说2 : 请注意,gets与fgets完全不同:不仅使用stdin作为源,而且它不包括结果字符串中的结束换行符,并且不允许指定str的最大大小( 这可能导致缓冲区溢出 )。 并且: 最新版本的C标准(2011年)明确地将此function从其规范中删除。 该函数在C ++中已弃用(截至2011年标准,遵循C99 + TC3)。 当然,现在通常建议将fgets替换为gets ,因为它的声明如下所示: char * fgets ( char * str, int num, FILE * stream ); 它需要一个尺寸参数。 这使它比gets更安全。 既然我不愿意花钱去下载或购买C11 standard ,那么任何人都可以了解弃用gets的原因及其对未来代码的意义吗? 当fgets更安全时,为什么它存在于同一个地方? 为什么它只是刚刚被弃用?

枚举对象设置为不等于其各自枚举常量的值

如果将枚举对象设置为不等于其各自枚举常量的值,则枚举对象具有什么值? 请考虑以下代码: enum foobar{ FOO = 1, BAR = 5 }; enum foobar baz = 5; enum foobar qux = 42; 变量baz被设置为整数值5 ,而变量qux被设置为整数值42 。 我怀疑变量baz将保持值BAR ,但我不确定变量qux 。 没有为枚举常量赋值42 ,那么当enum foobar变量设置为这样的值时会发生什么? C99标准是否明确了结果?

将指向数组的指针转换为指针

考虑以下C代码: int arr[2] = {0, 0}; int *ptr = (int*)&arr; ptr[0] = 5; printf(“%d\n”, arr[0]); 现在,很明显代码在常见编译器上打印5 。 但是,有人可以找到C标准中的相关部分,指出代码确实有效吗? 或者是代码未定义的行为? 我基本上要问的是,为什么&arr在转换为void *时&arr一样转换为void * ? 因为我相信代码相当于: int arr[2] = {0, 0}; int *ptr = (int*)(void*)&arr; ptr[0] = 5; printf(“%d\n”, arr[0]); 我在这里思考这个问题时发明了这个例子: 数组的指针到数组的重叠 ……但这显然是一个截然不同的问题。

指针指向非易失性对象的指针行为的要求

C11 6.7.3类型限定词,第7段,内容如下: 具有volatile限定类型的对象可能以实现未知的方式进行修改,或者具有其他未知的副作用。 因此,任何涉及这种对象的表达都应严格按照抽象机的规则进行评估,如5.1.2.3所述。 在以下示例中,第三行中访问的对象是否受上述规则约束? int x; volatile int *p = &x; *p = 42; 换句话说,lvalue *p具有类型volatile int的事实是否意味着正在访问volatile对象,或者p指向非易失性对象x的事实意味着编译器可以使用此知识进行优化并省略不稳定的访问? 由于它可能是有意义的,我感兴趣的特定用例不在普通C的范围内; 它涉及使用pre-C11结构(可以是内联asm或简称为黑盒子)进行线程同步的primefaces,用于primefaces比较和交换,具有以下习语: do { tmp = *p; new = f(tmp); } while (atomic_cas(p, tmp, new) != success); 这里指针p类型为volatile int * ,但是我关注当实际指向的对象是非易失性时会发生什么,特别是编译器是否可以*p tmp = *p的单个访问转换为两个访问以下forms: do { new = f(*p); } while (atomic_cas(p, *p, new) != success); 这显然会导致代码不正确。 […]

重新定义标准名称是不确定的行为?

很容易理解这样的代码是如何工作的: #include #define strcmp my_strcmp int my_strcmp(const char *, const char *) … strcmp(str1, str2); … 但这个问题是这在技术上是否正确。 从C11: 7.1.3.1(关于保留名称): … 如果包含任何相关标头,则保留以下任何子条款中的每个宏名称(包括未来的库方向)以供指定使用; 除非另有明确说明(见7.1.4)。 在以下任何子条款(包括未来的库方向)和errno中具有外部链接的所有标识符始终保留用作具有外部链接的标识符。 184 ) 在以下任何子条款中列出的具有文件范围的每个标识符(包括未来的库方向)保留用作宏名称,并且如果包含任何相关联的标题,则用作具有相同名称空间的文件范围的标识符。 184具有外部链接的保留标识符列表包括math_errhandling,setjmp,va_copy和va_end。 所以这意味着strcmp是一个保留字,因为包含了string.h 。 7.1.3.2: …如果程序在保留它的上下文中声明或定义标识符(除了7.1.4允许的标识符),或者将保留标识符定义为宏名称,则行为是未定义的。 现在这似乎说重新定义strcmp是未定义的行为,除了它在7.1.4中以某种方式允许。 7.1.4可能相关的内容是: 7.1.4.1: …标头中声明的任何函数可以另外实现为标头中定义的类函数宏,因此如果在包含标头时显式声明了库函数,则可以使用下面显示的技术之一来确保声明不受这种宏的影响。 通过将函数的名称括在括号中,可以在本地抑制函数的任何宏定义,因为该名称后面没有左括号,后面表示宏函数名称的扩展。 出于相同的语法原因,即使它也被定义为宏,也允许获取库函数的地址。 185 )使用#undef删除任何宏定义也将确保引用实际函数。 … 185这意味着实现应为每个库函数提供实际函数,即使它还为该函数提供宏。 7.1.4.2: 如果可以在不引用标头中定义的任何类型的情况下声明库函数,则允许声明该函数并使用它而不包括其关联的标头。 其余条款无关紧要。 我没有看到7.1.3.2所指的是“7.1.4允许的”,除了与函数相同的头中的库函数的定义,即标准头 ,作为宏。 总之,上面的代码是技术上未定义的行为吗? 如果没有包含string.h怎么样?

是否有可能在C中编写一致的malloc实现?

这是一个char数组可以与任何数据类型一起使用的后续内容吗? 我知道动态内存和malloc的常见实现,可以在维基百科上找到引用。 我也知道malloc返回的指针可以转换成程序员想要的任何东西,甚至没有警告,因为6.3.2.3指针中的标准状态§1 指向void的指针可以转换为指向任何不完整或对象类型的指针。 指向任何不完整或对象类型的指针可能会转换为指向void的指针并再次返回; 结果应该等于原始指针。 问题是假设我有一个没有malloc和free的独立环境,我如何在符合C的情况下构建这两个函数的实现? 如果我对标准采取一些自由,很容易: 从一个大字符数组开始 使用相当大的对齐(对于许多架构,8应该足够) 实现一个算法,从该数组返回地址,在该对齐,跟踪已分配的内容 – 在malloc实现中可以找到很好的例子? 问题是该实现返回的指针的有效类型仍然是char * 标准在同一段§7中说明 指向对象或不完整类型的指针可以转换为指向不同对象或不完整类型的指针。 如果结果指针未针对指向类型正确对齐,则行为未定义。 否则,当再次转换回来时,结果应该等于原始指针。 这似乎并不允许我假装被声明为简单字符的内容可以神奇地包含另一种类型,甚至是该数组的不同部分中的不同类型或同一部分中的不同时刻。 不同地解释引用这样的指针似乎是未定义的行为,并严格解释标准。 这就是为什么当你在字符串缓冲区中获得对象的字节表示时,常见的习语使用memcpy而不是别名,例如当你从网络流中读取它时。 那么如何在纯C中构建一个符合malloc的实现呢?

C99中“算术运算”的定义是什么?

在C99中,术语算术运算出现了16次,但我没有看到它的定义。 术语算术运算符仅在文本中出现两次(同样没有定义)但它确实出现在索引中: 算术运算符 添加剂,6.5.6,G.5.2 按位,6.5.10,6.5.11,6.5.12 递增和递减,6.5.2.4,6.5.3.1 乘法6.5.5,G.5.1 转变,6.5.7 一元,6.5.3.3 然后我们有+ – | & (二进制) ++ — * (二进制) / % << >> ~作为算术运算符,如果索引被认为是规范的! 也许我们应该将算术运算识别为算术运算符的使用。 但是F9.4.5表示sqrt()函数也是算术运算,详情请参考IEC 60559(又名IEEE754)。 因此必须有算术运算,而不仅仅是算术运算符的使用。

int main(){}(没有“void”)在ISO C中是否有效且可移植?

C标准为托管实现指定了两种main的定义forms: int main(void) { /* … */ } 和 int main(int argc, char *argv[]) { /* … */ } 它可以以与上述“等效”的方式定义(例如,您可以更改参数名称,将int替换为定义为int的typedef名称,或将char *argv[]替换为char **argv )。 它也可以“以某种其他实现定义的方式”定义 – 这意味着如果实现记录它们int main(int argc, char *argv[], char *envp)是有效的。 “在其他一些实施定义的方式”条款不在1989/1990标准中; 它是由1999标准添加的(但早期标准允许扩展,因此实现仍然允许其他forms)。 我的问题是:鉴于当前(2011)ISO C标准,是表格的定义 int main() { /* … */ } 对所有托管实现有效且可移植? (请注意,我没有解决void main或在C ++中没有使用括号的int main() 。这只是ISO C中的int main(void)和int main()之间的区别。)

(int *)0是否为空指针?

这可以被认为是这个问题的扩展(我只对C感兴趣,但是添加C ++来完成扩展) 6.3.2.3.3中的C11标准说: 值为0的整型常量表达式或类型为void *的表达式称为空指针常量。 我个人对此的看法是0和(void *)0表示空指针,其整数值实际上可能不是0,但不包括0强制转换为任何其他类型。 但是,标准然后继续: 如果将空指针常量转换为指针类型,则生成的指针称为空指针 ,… 它覆盖(int *)0作为空指针,因为强制转换是转换方法下列出的显式转换 (C11,6.3)。 然而,令我惊讶的是以下这句话 …或者这样的表达式转换为void * … 有了上述语义,这句话​​似乎完全没用。 问题是,这句话完全没用吗? 如果没有,它有什么影响? 因此, (int *)0为空指针? 可以帮助讨论的另一个问题如下。 是(long long)123被认为是“123转换为long long ”,或“123 long long型long long ”。 换句话说, (long long)123是否有任何转换? 如果没有,那么上面的第二个引号不会覆盖(int *)0作为空指针。

限制C中main的参数个数

我们传递给C中的main()的参数数量是否有限制? 众所周知,它被定义为int main(int argc, char *argv[]) 。 当我调用程序时,我可以传递这样的参数: $ prog.exe arg1 arg2 arg3…..argn 我们可以用这种方式提供给main()的参数个数是否有上限?