我正在尝试创建自己的自定义断言。但是,我希望我的断言自动包含所有相关变量。这对我来说似乎真的很基本,我已经搜索了大约一个小时,但我似乎找不到访问所有相关堆栈帧变量的方法。有谁知道如何获得这些变量?
仅供参考 - 我不需要访问调试器中的变量,我需要以编程方式访问它们。我想将它们与崩溃报告一起上传,以提供有关崩溃的更多信息。我也知道我可以手动打印出来……这正是我想要避免的。
我正在尝试创建自己的自定义断言。但是,我希望我的断言自动包含所有相关变量。这对我来说似乎真的很基本,我已经搜索了大约一个小时,但我似乎找不到访问所有相关堆栈帧变量的方法。有谁知道如何获得这些变量?
仅供参考 - 我不需要访问调试器中的变量,我需要以编程方式访问它们。我想将它们与崩溃报告一起上传,以提供有关崩溃的更多信息。我也知道我可以手动打印出来……这正是我想要避免的。
您基本上是在要求重新发明一大块调试器。
没有符号,就没有任何东西可以询问来确定本地框架的布局。即使使用符号,优化器也很可能会踩到任何局部变量,因为一旦确定帧内不再需要变量,优化器就会随心所欲地重新使用堆栈槽。
请注意,许多崩溃根本无法被捕获,或者,如果被捕获,它们发生的框架早就被破坏了。
既然您提到您正在创建一个自定义断言,那么当您以编程方式检测到事情已经偏离轨道时,听起来您真的不想像转储本地框架的快照那样自省崩溃。虽然确实没有自动报告本地堆栈状态的方法,但您可以执行以下操作:
{ ... some function ....
... local variables ...
#define reportblock ^{ ... code that summarizes locals ... ; return summary; }
YourAssert( cond, "cond gone bad. summary: %@", reportblock());
}
请注意,#define 确保每个 YourAssert() 捕获断言时的状态。另请注意,上述内容可能会对性能产生潜在的重大影响。
另请注意,我刚刚编写了该代码。看起来它值得调查,但由于多种原因可能证明不可行。
我决定将其添加为单独的答案,因为它使用与我的另一个相同的方法,但这次使用的是所有 ObjC 代码。不幸的是,您仍然必须像以前一样重新声明所有堆栈变量,但希望现在它可以更好地与您现有的代码库一起使用。
堆栈变量.h:
#import <Foundation/Foundation.h>
#define LOCAL_VAR(p_type, p_name, p_value)\
p_type p_name = p_value;\
StackVariable *__stack_ ## p_name = [[StackVariable alloc] initWithPointer:&p_name\
size:sizeof(p_type)\
name:#p_name\
type:#p_type\
file:__FILE__\
line:__LINE__];\
(void) __stack_ ## p_name;
@interface StackVariable : NSObject
-(id) initWithPointer:(void *) ptr
size:(size_t) size
name:(const char *) name
type:(const char *) type
file:(const char *) file
line:(const int) line;
+(NSString *) dump;
@end
堆栈变量.m:
#import "StackVariable.h"
static NSMutableArray *stackVariables;
@implementation StackVariable {
void *_ptr;
size_t _size;
const char *_name;
const char *_type;
const char *_file;
int _line;
}
-(id) initWithPointer:(void *)ptr size:(size_t)size name:(const char *)name type:(const char *)type file:(const char *)file line:(int)line
{
if (self = [super init]) {
if (stackVariables == nil) {
stackVariables = [NSMutableArray new];
}
_ptr = ptr;
_size = size;
_name = name;
_type = type;
_file = file;
_line = line;
[stackVariables addObject:[NSValue valueWithNonretainedObject:self]];
}
return self;
}
-(NSString *) description {
NSMutableString *result = [NSMutableString stringWithFormat:@"%s:%d - %s %s = { ", _file, _line, _type, _name];
const uint8_t *bytes = (const uint8 *) _ptr;
for (size_t i = 0; i < _size; i++) {
[result appendFormat:@"%02x ", bytes[i]];
}
[result appendString:@"}"];
return result;
}
+(NSString *) dump {
NSMutableString *result = [NSMutableString new];
for (NSValue *value in stackVariables) {
__weak StackVariable *var = [value nonretainedObjectValue];
[result appendString:[var description]];
[result appendString:@"\n"];
}
return result;
}
-(void) dealloc {
[stackVariables removeObject:[NSValue valueWithNonretainedObject:self]];
}
@end
例子:
#include "StackVariable.h"
int temp() {
LOCAL_VAR(int, i_wont_show, 0);
return i_wont_show;
}
int main(){
LOCAL_VAR(long, l, 15);
LOCAL_VAR(int, x, 192);
LOCAL_VAR(short, y, 256);
temp();
l += 10;
puts([[StackVariable dump] UTF8String]);
}
输出:
/Users/rross/Documents/TestProj/TestProj/main.m:676 - 长 l = { 19 00 00 00 00 00 00 00 } /Users/rross/Documents/TestProj/TestProj/main.m:677 - int x = { c0 00 00 00 } /Users/rross/Documents/TestProj/TestProj/main.m:678 - 短 y = { 00 01 }
这需要为您想要测试的任何文件启用 ARC(以及所有它的魔法),否则您将不得不手动释放__stack_
变量,这不会很漂亮。
但是,它现在为您提供了变量的十六进制转储(而不是怪异的指针),如果您真的足够努力(使用__builtin_types_compatible
),它可以检测结果是否是一个对象,并打印出来。
再一次,线程会搞砸这个问题,但解决这个问题的一个简单方法是创建 a NSDictionary
of NSArrays
,并以 aNSThread
作为键。让它有点慢,但老实说,如果你在 C++ 版本上使用它,你不会追求性能。
如果你愿意使用Objective-C++,那么这绝对是可能的,只要你也愿意以不同的方式声明你的变量,并且明白你只能用这种方法来抓取你自己的变量。
另请注意,它会使用额外的__stack_
变量增加堆栈帧的大小,这可能会导致内存问题(尽管我个人对此表示怀疑)。
它不适用于某些结构,例如 for 循环,但对于 95% 的情况,这应该可以满足您的需要:
#include <vector>
struct stack_variable;
static std::vector<const stack_variable *> stack_variables;
struct stack_variable {
void **_value;
const char *_name;
const char *_type;
const char *_file;
const char *_line;
private:
template<typename T>
stack_variable(const T& value, const char *type, const char *name, const char *file, const char *line) : _value((void **) &value), _type(type), _name(name), _file(file), _line(line) {
add(*this);
}
static inline void add(const stack_variable &var) {
stack_variables.push_back(static_cast<const stack_variable *>(&var));
}
static inline void remove(const stack_variable &var) {
for (auto it = stack_variables.begin(); it != stack_variables.end(); it++) {
if ((*it) == &var) {
stack_variables.erase(it);
return;
}
}
}
public:
template<typename T>
static inline stack_variable create(const T& value, const char *type, const char *name, const char *file, const char *line) {
return stack_variable(value, type, name, file, line);
}
~stack_variable() {
remove(*this);
}
void print() const {
// treat the value as a pointer
printf("%s:%s - %s %s = %p\n", _file, _line, _type, _name, *_value);
}
static void dump_vars() {
for (auto var : stack_variables) {
var->print();
}
}
};
#define __LINE_STR(LINE) #LINE
#define _LINE_STR(LINE) __LINE_STR(LINE)
#define LINE_STR _LINE_STR(__LINE__)
#define LOCAL_VAR(type, name, value)\
type name = value;\
stack_variable __stack_ ## name = stack_variable::create<type>(name, #type, #name, __FILE__, LINE_STR);\
(void) __stack_ ## name;
例子:
int temp() {
LOCAL_VAR(int, i_wont_show, 0);
return i_wont_show;
}
int main(){
LOCAL_VAR(long, l, 15);
LOCAL_VAR(int, x, 192);
LOCAL_VAR(short, y, 256);
temp();
l += 10;
stack_variable::dump_vars();
}
输出(请注意小于 sizeof(void *) 的值的垃圾额外字节,对此我无能为力):
/Users/rross/Documents/TestProj/TestProj/main.mm:672 - 长 l = 0x19 /Users/rross/Documents/TestProj/TestProj/main.mm:673 - int x = 0x5fbff8b8000000c0 /Users/rross/Documents/TestProj/TestProj/main.mm:674 - 短 y = 0xd000000010100
然而,线程将彻底搞砸,因此在多线程环境中,这(几乎)毫无价值。