我想知道如何完成这个难题。我需要能够打印通常反向打印的内容。通常这会打印hello there
。我需要它来打印there hello
。我只允许在评论的地方添加代码。
以下是我的一些不起作用的想法。
stdlib.h
没有包含在内。 goto
,因为我无法添加标签,而goto
只能在一行中添加printf
并提前退出程序。 #include <stdio.h>
void f1(); void f2(); void f3();
int main() { f1(); printf("\n"); return 0; }
void f1() { f2(); printf(" there "); }
void f2() { f3(); printf(" hello "); }
void f3(){
int x;
//Can add whatever under here
}
答案 0 :(得分:2)
我认为int x;
的唯一目的是在不使用内联汇编的情况下获取堆栈指针。
关于如何完全执行此操作的解决方案将取决于您的平台,您使用的编译器以及您使用的优化级别。
我首先要说你需要分析调用堆栈。
你可以这样做 -
int i;
for (i = 0; i< 10; i++) {
printf ("%p\n", *(void**)((char*) &x - i * 8)); // I am assumming 64 bit machines. If 32 bit replace 8 with 4
}
这将为您提供堆栈上的前10个8字节值。现在你需要找到两个看起来像返回地址的东西。识别它们的一种方法是打印f1和f2的函数指针值,并查看接近它们的值。
您现在知道存储它们的索引。继续交换它们。
对于交换它们,请说索引是12和14。
然后你可以这样做 -
*(void**)&x = *((void**)&x + 12);
*((void**)&x + 12) = *((void**)&x + 14);
*((void**)&x + 14) = *(void**)&x;
另外,请确保在获得索引后不要更改堆栈布局。这意味着不要删除/添加任何变量。不要申请&amp;运算符到任何新变量(或从中删除)并且不删除任何函数调用。
另外一个建议 - 你可以声明另一个unsigned long long y而不是使用int x,而不是使用int x。因为它有足够的字节来保存指针(在64位机器上)。通常在int x的情况下也会有填充,这样可以避免问题,但是安全。
答案 1 :(得分:2)
这是一个不依赖于堆栈操作的替代解决方案。根本&#39;诀窍&#39;是程序提供自己的printf()
实现,而不是使用标准库。
在gcc(Mingw,Linux x86和Linux x64)和MSVC上测试:
#include <stdio.h>
void f1(); void f2(); void f3();
int main() { f1(); printf("\n"); return 0; }
void f1() { f2(); printf(" there "); }
void f2() { f3(); printf(" hello "); }
void f3(){
int x;
//Can add whatever under here
return;
}
void putstr( char const* s)
{
for (;*s;++s) {
putchar(*s);
}
}
int printf(char const* fmt, ...)
{
static char const* pushed_fmt = 0;
if (*fmt == '\n') {
putstr(fmt);
return 0;
}
if (pushed_fmt == 0) {
pushed_fmt = fmt;
return 0;
}
putstr(fmt);
putstr(pushed_fmt);
return 0;
}
答案 2 :(得分:1)
我认为这个想法是从f3()开始,堆栈上的返回地址一直向上,到目前为止还没有打印过。
你必须使用堆栈内容,以便f3()返回f1(),然后返回到f2()。
你能从这里拿走吗?根据编译器的不同,将有不同的方法来实现此目的。内联汇编可能需要也可能不需要。
编辑:专门针对GCC,请参阅the GCC return address intrinsics。
答案 3 :(得分:1)
这应该是一个可移植的解决方案,不会弄乱堆栈和返回地址。很可能不是那些写这个挑战的人所期望的,但开箱即用的方式更有趣。
void f3(){
int x;
//Can add whatever under here
static count = 0;
static char buf[256];
if(count==0) {
setvbuf(stdout, buf, _IOFBF, sizeof(buf));
int atexit (void (*func)(void));
atexit(f3);
count = 1;
} else {
const char *src = " there hello \n";
char *dest = buf;
for(; *src;) *dest++ = *src++;
}
}
这首先使用setvbuf
将stdout
缓冲区替换为我们提供的缓冲区,然后将其切换为完全缓冲(而不是行缓冲),以确保在结束之前不会发生刷新该程序(注意尚未写入任何输出,因此调用setvbuf
是合法的)。我们还调用atexit
以确保在程序结束之前调用(我们没有stdlib.h
,但是当已知所需的原型时谁需要头文件)。
当我们再次被调用时(感谢atexit
),已经调用了两个printf
,但缓冲区尚未被刷新。我们用我们感兴趣的字符串(它和已经写好的字符串一样)直接替换它的内容,然后返回。随后的隐式fclose
将转储我们缓冲区的修改内容,而不是printf
所写的内容。