函数调用期间是否实际传递了未命名参数?

发布于 2024-11-08 19:59:00 字数 573 浏览 0 评论 0原文

template <typename TAG>
fn(int left, TAG, int right)
{
}

fn(0, some_type_tag(), 1);
/* or */
fn(0,int(), 1); // where the primitive, int, is not empty.

编辑:这个问题有两种观点。

  1. 函数声明与定义。声明可能不会命名参数,但声明可能会命名。这不是我们感兴趣的观点。
  2. 模板方面,特别是在元编程中。所讨论的参数是一个标签,用于从特征中提取元结构。这就是参数未命名的原因,我只关心编译时信息 - 标记的类型。

/编辑

我的标签通常是空结构,但是在我的代码的某些部分,它们是原始类型的 typedef。所以,我有兴趣知道现代编译器是否真的会传递参数。这有两个方面。

  1. 调整堆栈大小,考虑未命名参数类型的大小。
  2. 实际上用传递的值构建堆栈。

让我们保留 gcc 4.5 和 msvc 2008+

template <typename TAG>
fn(int left, TAG, int right)
{
}

fn(0, some_type_tag(), 1);
/* or */
fn(0,int(), 1); // where the primitive, int, is not empty.

EDIT: There are two perspectives to this question.

  1. Function declaration vs definition. The declaration might not name the parameter, but the declaration might do.This isn't the perspective of interest.
  2. The template perspect, spectifically in meta-programming. The parameter In question is a tag used to pull out a meta-structure out of a trait. This is why the parameter is unnamed, I only care about the compile-time information - the type of the tag.

/EDIT

My tags are generally empty-structs, however in some parts of my code they are typedefs of primitive types.So, I'm interested to know if modern compilers will actually pass a parameter. This has two aspects.

  1. Sizing the stack, taking into account the size of the unnamed parameter type.
  2. Actually constructing the stack with the passed value.

Lets keep it to gcc 4.5 and msvc 2008+

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

爱你不解释 2024-11-15 19:59:00

C++ 有单独的翻译。由于参数可以在声明中命名,但不能在函数定义中命名,反之亦然,因此编译器通常无法知道省略函数参数是否安全。当它们都在同一个翻译单元中时,所有内容都可以内联,并且参数名称与优化完全无关。

[已添加]

单独的翻译对于这种特定情况可能并不重要,但添加此类优化的编译器构建者必须关心。如果这种优化破坏了完全有效的代码,他们就不会进行这种优化。

对于模板,模板函数的类型必须等于非模板函数的类型,否则无法获取其地址并将其分配给函数指针。同样,您必须考虑单独的翻译。仅仅因为您在这个 TU 中没有获取 foo 的地址,并不意味着您不会在另一个 TU 中获取。

C++ has separate translation. Since the parameter can be named in the declaration but not in the function definition and vice versa, there's generally no way whether the compiler knows whether it's safe to omit the function argument. When it's all in the same translation unit, everything could be inlined and the argument name is entirely irrelevant to optimization.

[Added]

The seperate translation may not matter to this specific case, but a compiler builder that would add such an optimization must care. They're not going to put in such optimizations if it breaks perfectly valid code.

As for templates, it's necessary that the type of a template function is equal to the type of a non-template function, else it's impossible to take its address and assign it to a function pointer. Again, you have to take into account seperate translation. Just because you don't take the address of foo<int> in this TU doesn't mean you won't in another.

厌味 2024-11-15 19:59:00

这实际上是一个非常有趣的问题。

首先,请注意,我们使用的是命令式语言,这意味着当您请求某些东西(甚至是无用的,例如构造一个未使用的对象)时,编译器需要遵守,除非它可以提出等效的形式。基本上,如果可以证明这样做不会改变程序的含义,则可以删除该参数。

当您编写函数调用时,最终可能会发生两件事:

  • 要么是内联函数
  • ,要么是实际发出 call

如果它是内联,则没有参数通过,这实际上意味着如果编译器可以证明所涉及的构造函数和析构函数没有执行任何重要的工作,则可以删除(甚至不构建)未使用的对象。它适用于标签结构。

当发出调用时,它是按照特定的调用约定发出的。每个编译器都有自己的一组调用约定,指定如何传递各种参数(this 指针等),通常会尝试利用可用的寄存器。

由于仅使用函数的声明来确定调用约定(单独的编译模型),因此需要实际传递对象...

但是,如果我们谈论的是空结构,没有方法,也没有状态,那么这只是一些未初始化的内存。它应该不会花费太多,但它确实需要堆栈空间(至少,保留它)。

使用 llvm 试用的演示:

struct tag {};

inline int useless(int i, tag) { return i; }

void use(tag);

int main() {
  use(tag());
  return useless(0, tag());
}

给出:

%struct.tag = type <{ i8 }>

define i32 @main() {
entry:
  ; allocate space on the stack for `tag`
  %0 = alloca %struct.tag, align 8                ; <%struct.tag*> [#uses=2]

  ; get %0 address
  %1 = getelementptr inbounds %struct.tag* %0, i64 0, i32 0 ; <i8*> [#uses=1]

  ; 0 initialize the space used for %0
  store i8 0, i8* %1, align 8

  ; call the use function and pass %0 by value
  call void @_Z3use3tag(%struct.tag* byval %0)
  ret i32 0
}

declare void @_Z3use3tag(%struct.tag* byval)

注意:

  • 如何删除对 useless 的调用,并且没有为其构建参数
  • 如何无法删除对 use 的调用,因此空间是为临时分配的(我希望新版本不要0-初始化内存)

It's quite an interesting question actually.

First of all, note that we are in an imperative language, meaning that when you ask for something (even useless, such as constructing an unused object) then the compiler need to comply unless it can come up with an equivalent form. Basically, it could elide the parameter if it could prove that doing so would not change the meaning of the program.

When you write a function call, two things may happen (in the end):

  • either it is inlined
  • or a call is actually emitted

If it is inlined, then no parameter is passed, which effectively means that unused objects can be removed (and not even built) if the compiler can prove that the constructors and destructors involved do not perform any significant work. It works well for tags structures.

When a call is emitted, it is emitted with a specific calling convention. Each compiler has its own set of calling conventions which specify how to pass the various arguments (this pointer, etc...), generally trying to take advantage of the available registers.

Since only the declaration of the function is used to determine the calling convention (separate compilation model), then it is necessary to actually pass the object...

However, if we are talking about an empty structure, with no method and no state, then this is just some uninitialized memory. It should not cost much, but it does require stack space (at least, reserving it).

Demo using the llvm tryout:

struct tag {};

inline int useless(int i, tag) { return i; }

void use(tag);

int main() {
  use(tag());
  return useless(0, tag());
}

Gives:

%struct.tag = type <{ i8 }>

define i32 @main() {
entry:
  ; allocate space on the stack for `tag`
  %0 = alloca %struct.tag, align 8                ; <%struct.tag*> [#uses=2]

  ; get %0 address
  %1 = getelementptr inbounds %struct.tag* %0, i64 0, i32 0 ; <i8*> [#uses=1]

  ; 0 initialize the space used for %0
  store i8 0, i8* %1, align 8

  ; call the use function and pass %0 by value
  call void @_Z3use3tag(%struct.tag* byval %0)
  ret i32 0
}

declare void @_Z3use3tag(%struct.tag* byval)

Note:

  • how the call to useless was removed, and no argument is build for it
  • how to call to use cannot be removed, and therefore space is allocated for the temporary (I hope that the new versions don't 0-initialize the memory)
北城孤痞 2024-11-15 19:59:00

参数是否命名对函数签名没有影响,编译器应该将其传入。请考虑函数声明中的未命名参数可能在定义中命名。

现在,在像上面这样的模板的特殊情况下,编译器很可能会内联代码,在这种情况下,不会传递任何参数,并且未命名的参数将不起作用。

如果您想要做的是标记以解析不同的重载,那么您始终可以回退到指针,这样即使它被传入,成本也将是最小的。

Whether the parameter is named or not has no effect on the function signature, and the compiler should pass it in. Consider that an unnamed parameter in the declaration of a function might be named in the definition.

Now, in the particular case of templates like the one above, chances are that the compiler will inline the code, in which case no arguments will be passed, and the unnamed argument will have no effect.

If what you are trying to do is tagging to resolve to different overloads, you can always fall back to a pointer, so that even if it is passed in, the cost will be minimal.

锦爱 2024-11-15 19:59:00

好问题,但你必须尝试使用​​你的编译器。理论上,如果不使用某个参数,则不必在堆栈中分配该参数。然而,调用者必须知道如何调用它,所以我的猜测是该元素实际上是在堆栈中分配的。

Good question, but you'll have to try on your compiler. In theory, if a parameter is not used, it doesn't have to be allocated in the stack. However, callers have to know how to call it, so my guess is that the element gets actually allocated in the stack.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文