我偶然发现了以下代码片段:
#include <iostream>
#include <string>
using namespace std;
class First
{
string *s;
public:
First() { s = new string("Text");}
~First() { delete s;}
void Print(){ cout<<*s;}
};
int main()
{
First FirstObject;
FirstObject.Print();
FirstObject.~First();
}
Run Code Online (Sandbox Code Playgroud)
该文本表示此代码段应该导致运行时错误.现在,我对此并不十分肯定,所以我尝试编译并运行它.有效.奇怪的是,尽管所涉及的数据非常简单,但在打印"文本"之后程序结结巴巴,并且仅在一秒钟之后完成.
我添加了一个要打印到析构函数的字符串,因为我不确定显式调用这样的析构函数是否合法.程序打印两次字符串.所以我的猜测是析构函数被调用两次,因为正常的程序终止不知道显式调用并试图再次销毁对象.
一个简单的搜索确认显式调用自动化对象上的析构函数是危险的,因为第二次调用(当对象超出范围时)具有未定义的行为.所以我很幸运,我的编译器(VS 2017)或这个特定的程序.
关于运行时错误,文本是否完全错误?或者运行时错误真的很常见吗?或者也许我的编译器实现了某种针对这类事情的warding机制?
c++ destructor lifetime undefined-behavior explicit-destructor-call
我有一个发出以下警告的功能(见下文):
'va_start'的第二个参数不是最后命名的参数
它意味着什么以及如何删除它?
功能如下:
static int ui_show_warning(GtkWindow *parent, const gchar *fmt, size_t size, ...)
{
GtkWidget *dialog = NULL;
va_list args = NULL;
int count = -1;
char *msg = NULL;
if((msg = malloc(size + 1)) == NULL)
return -12;
va_start(args, fmt);
if((count = snprintf(msg, size, fmt, args)) < 0)
goto outer;
dialog = gtk_message_dialog_new(parent,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_WARNING,
GTK_BUTTONS_OK,
"%s", msg);
(void) gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
outer: {
if(args != NULL)
va_end(args);
if(msg != NULL)
free(msg);
return count;
}
}
Run Code Online (Sandbox Code Playgroud) 假设unsigned int没有陷阱表示,请执行下面标记为(A)和(B)的语句中的任何一个或两个引发未定义的行为,为什么或为什么不这样做,以及(特别是如果您认为其中一个定义明确但另一个不定义),您认为标准中存在缺陷吗?我主要对当前版本的C标准(即C2011)感兴趣,但如果在标准的旧版本或C++中有所不同,我也想知道这一点.
(_Alignas在这个程序中用于消除因对齐不充分而导致的任何UB问题.我在解释中讨论的规则虽然没有说明对齐.)
#include <stdlib.h>
#include <string.h>
int main(void)
{
unsigned int v1, v2;
unsigned char _Alignas(unsigned int) b1[sizeof(unsigned int)];
unsigned char *b2 = malloc(sizeof(unsigned int));
if (!b2) return 1;
memset(b1, 0x55, sizeof(unsigned int));
memset(b2, 0x55, sizeof(unsigned int));
v1 = *(unsigned int *)b1; /* (A) */
v2 = *(unsigned int *)b2; /* (B) */
return !(v1 == v2);
}
Run Code Online (Sandbox Code Playgroud)
我对C2011的解释是(A)引发未定义的行为,但(B)定义明确(存储未指定的值v2),因为:
memset被定义(§7.24.6.1)写入它的第一个参数作为-如果通过与字符类型,它被允许用于两左值b1和b2每特殊情况下,在§6.5p7的底部.
该对象b1具有声明的类型unsigned char[n].因此,它的有效访问类型也是unsigned char[n] …
我知道诸如x = x++ + ++x调用未定义行为之类的事情,因为变量在同一序列点内被多次修改。这在这篇文章中进行了彻底的解释为什么这些构造使用前增量和后增量未定义行为?
但是考虑像printf("foo") + printf("bar"). 该函数printf返回一个int,因此该表达式在这个意义上是有效的。但是+标准中没有规定运算符的求值顺序,所以不清楚这会打印foobar还是barfoo。
但我的问题是这是否也是未定义的行为。
我知道C++中的reinterpret_cast可以这样使用:
float a = 0;
int b = *reinterpret_cast<int*>(&a);
Run Code Online (Sandbox Code Playgroud)
但是为什么不能直接施展呢?
float a = 0;
int b = reinterpret_cast<int>(a);
error: invalid cast from type 'float' to type 'int'
Run Code Online (Sandbox Code Playgroud) 据我所知,在C++ 11之前,字符串文字在C和C++之间几乎完全相同.
现在,我承认C和C++在处理宽字符串文字方面存在差异.
我能找到的唯一区别在于通过字符串文字初始化数组.
char str[3] = "abc"; /* OK in C but not in C++ */
char str[4] = "abc"; /* OK in C and in C++. Terminating zero at str[3] */
Run Code Online (Sandbox Code Playgroud)
技术差异只在C++中很重要.在C++ "abc"中const char [4],它在C中char [4].然而,C++有一个特殊的规则,允许转化为const char *然后char *保留C兼容性直至C++ 11不再施加特殊规则时.
文字允许长度的差异.但是,实际上,编译C和C++代码的任何编译器都不会强制执行较低的C限制.
我有一些有趣的链接适用:
还有其他差异吗?
我今天在服务器上遇到了一些问题,现在我已经把它归结为它无法摆脱遇到段错误的进程.
在该过程发生seg-fault之后,该过程只是一直悬挂,而不是被杀死.
应该导致错误的测试Segmentation fault (core dumped).
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
char *buf;
buf = malloc(1<<31);
fgets(buf, 1024, stdin);
printf("%s\n", buf);
return 1;
}
Run Code Online (Sandbox Code Playgroud)
使用编译和设置权限gcc segfault.c -o segfault && chmod +x segfault.
在有问题的服务器上运行此(并按下输入1次)会导致它挂起.我也在另一台具有相同内核版本(和大多数相同的软件包)的服务器上运行它,它得到seg-fault然后退出.
以下是strace ./segfault在两台服务器上运行后的最后几行.
糟糕的服务器
"\n", 1024) = 1
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
# It hangs here....
Run Code Online (Sandbox Code Playgroud)
工作服务器
"\n", 1024) = 1
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
+++ killed by SIGSEGV (core dumped) +++ …Run Code Online (Sandbox Code Playgroud) 在c中,可以使用像这样的指针来改变const:
//mainc.c
#include <stdio.h>
int main(int argc, char** argv) {
const int i = 5;
const int *cpi = &i;
printf(" 5:\n");
printf("%d\n", &i);
printf("%d\n", i);
printf("%d\n", cpi);
printf("%d\n", *cpi);
*((int*)cpi) = 8;
printf(" 8?:\n");
printf("%d\n", &i);
printf("%d\n", i);
printf("%d\n", cpi);
printf("%d\n", *cpi);
}
Run Code Online (Sandbox Code Playgroud)
如果我们在c ++中尝试相同:
//main.cpp
#include <iostream>
using std::cout;
using std::endl;
int main(int argc, char** argv) {
const int i = 5;
const int *cpi = &i;
cout << " 5:" << '\n';
cout << &i << …Run Code Online (Sandbox Code Playgroud) 内存泄漏的基本概念是代码执行期间新/删除操作的不匹配,可能是由于错误的编码实践,也可能是在跳过删除操作时出现错误的情况.
但最近我在一次采访中被问到一个问题,关于内存泄漏的其他方式.我没有答案.它是什么?
我的C++知识有些零碎.我正在重新编写一些代码.我更改了一个函数来返回对类型的引用.在里面,我根据传入的标识符查找对象,然后返回对象的引用(如果找到).当然,如果我找不到对象,我会遇到要返回的问题,并且在环顾网络时,许多人声称在C++中返回"空引用"是不可能的.基于这个建议,我尝试了返回成功/失败布尔值的技巧,并使对象引用为out参数.但是,我遇到了需要初始化我将作为实际参数传递的引用的障碍,当然也没有办法做到这一点.我退回到通常只返回指针的方法.
我问了一位同事.他经常使用以下技巧,这是最新版本的Sun编译器和gcc都接受的:
MyType& someFunc(int id)
{
// successful case here:
// ...
// fail case:
return *static_cast<MyType*>(0);
}
// Use:
...
MyType& mt = somefunc(myIdNum);
if (&mt) // test for "null reference"
{
// whatever
}
...
Run Code Online (Sandbox Code Playgroud)
我一直在维护这个代码库,但我发现我没有足够的时间来查找我想要的语言的小细节.我一直在挖掘我的参考书,但这个答案让我望而却步.
现在,我在几年前开设了一门C++课程,其中我们强调在C++中,一切都是类型,所以我在考虑事情时会尽量记住这一点.解构表达式:" static_cast <MyType >(0);",在我看来,我们确实采用了字面零,将其转换为指向MyType的指针(使其成为空指针),然后应用解除引用运算符分配给引用类型(返回类型)的上下文,它应该为我提供对指针指向的同一对象的引用.这看起来像是向我返回一个空引用.
任何解释为什么这个工作(或为什么不应该)的建议将不胜感激.
谢谢,查克