the function Passing and returning

本文深入探讨了C和C++中通过值传递参数和返回值的底层机制,特别是在处理大型对象时如何解决堆栈管理和内存布局的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

From <thinking in C++>      Page:481


Passing & returning by value

To understand the need for the copy-constructor, consider the way 
C handles passing and returning variables by value during function 
calls. If you declare a function and make a function call, 
int f(int x, char c); 
int g = f(a, b); 
how does the compiler know how to pass and return those 
variables? It just knows! The range of the types it must deal with is 
so small – char, int, float, double, and their variations – that this 
information is built into the compiler. 
If you figure out how to generate assembly code with your 
compiler and determine the statements generated by the function 
call to f( ), you’ll get the equivalent of: 
push b 
push a 
call f() 
add sp,4 
mov g, register a 
This code has been cleaned up significantly to make it generic; the 
expressions for band awill be different depending on whether the 
variables are global (in which case they will be _band _a) or local 
(the compiler will index them off the stack pointer). This is also true 
for the expression for g. The appearance of the call to f( )will 
depend on your name-decoration scheme, and “register a” depends 
on how the CPU registers are named within your assembler. The 
logic behind the code, however, will remain the same. 
In C and C++, arguments are first pushed on the stack from right to 
left, then the function call is made. The calling code is responsible 
11: References & the Copy-Constructor  481 
for cleaning the arguments off the stack (which accounts for the 
add sp,4). But notice that to pass the arguments by value, the 
compiler simply pushes copies on the stack – it knows how big 
they are and that pushing those arguments makes accurate copies 
of them. 
The return value of f( )is placed in a register. Again, the compiler 
knows everything there is to know about the return value type 
because that type is built into the language, so the compiler can 
return it by placing it in a register. With the primitive data types in 
C, the simple act of copying the bits of the value is equivalent to 

copying the object. 


Passing & returning large objects
But now consider user-defined types. If you create a class and you 
want to pass an object of that class by value, how is the compiler 
supposed to know what to do? This is not a type built into the 
compiler; it’s a type you have created. 
To investigate this, you can start with a simple structure that is 
clearly too large to return in registers: 
//: C11:PassingBigStructures.cpp 
struct Big { 
char buf[100]; 
int i; 
long d; 
} B, B2; 
Big bigfun(Big b) { 
b.i = 100; // Do something to the argument 
return b; 

int main() { 
B2 = bigfun(B); 
} ///:~ 
Decoding the assembly output is a little more complicated here 
because most compilers use “helper” functions instead of putting 
482 Thinking in C++  www.BruceEckel.com 
all functionality inline. In main( ), the call to bigfun( )starts as you 
might guess – the entire contents of Bis pushed on the stack. (Here, 
you might see some compilers load registers with the address of 
the Bigand its size, then call a helper function to push the Bigonto 
the stack.) 
In the previous code fragment, pushing the arguments onto the 
stack was all that was required before making the function call. In 
PassingBigStructures.cpp , however, you’ll see an additional 
action: the address of B2is pushed before making the call, even 
though it’s obviously not an argument. To comprehend what’s 
going on here, you need to understand the constraints on the 
compiler when it’s making a function call. 
Function-call stack frame
When the compiler generates code for a function call, it first pushes 
all the arguments on the stack, then makes the call. Inside the 
function, code is generated to move the stack pointer down even 
farther to provide storage for the function’s local variables. 
(“Down” is relative here; your machine may increment or 
decrement the stack pointer during a push.) But during the 
assembly-language CALL, the CPU pushes the address in the 
program code where the function call came from, so the assemblylanguage RETURN can use that address to return to the calling 
point. This address is of course sacred, because without it your 
program will get completely lost. Here’s what the stack frame looks 
like after the CALL and the allocation of local variable storage in 
the function: 
Function arguments
Return address
Local variables
11: References & the Copy-Constructor  483 
The code generated for the rest of the function expects the memory 
to be laid out exactly this way, so that it can carefully pick from the 
function arguments and local variables without touching the return 
address. I shall call this block of memory, which is everything used 
by a function in the process of the function call, the function frame. 
You might think it reasonable to try to return values on the stack. 
The compiler could simply push it, and the function could return 
an offset to indicate how far down in the stack the return value 
begins. 
Re-entrancy
The problem occurs because functions in C and C++ support 
interrupts; that is, the languages are re-entrant. They also support 
recursive function calls. This means that at any point in the 
execution of a program an interrupt can occur without breaking the 
program. Of course, the person who writes the interrupt service 
routine (ISR) is responsible for saving and restoring all the registers 
that are used in the ISR, but if the ISR needs to use any memory 
further down on the stack, this must be a safe thing to do. (You can 
think of an ISR as an ordinary function with no arguments and 
voidreturn value that saves and restores the CPU state. An ISR 
function call is triggered by some hardware event instead of an 
explicit call from within a program.) 
Now imagine what would happen if an ordinary function tried to 
return values on the stack. You can’t touch any part of the stack 
that’s above the return address, so the function would have to push 
the values below the return address. But when the assemblylanguage RETURN is executed, the stack pointer must be pointing 
to the return address (or right below it, depending on your 
machine), so right before the RETURN, the function must move the 
stack pointer up, thus clearing off all its local variables. If you’re 
trying to return values on the stack below the return address, you 
become vulnerable at that moment because an interrupt could 
come along. The ISR would move the stack pointer down to hold 
484 Thinking in C++  www.BruceEckel.com 
its return address and its local variables and overwrite your return 
value. 
To solve this problem, the caller couldbe responsible for allocating 
the extra storage on the stack for the return values before calling 
the function. However, C was not designed this way, and C++ 
must be compatible. As you’ll see shortly, the C++ compiler uses a 
more efficient scheme. 
Your next idea might be to return the value in some global data 
area, but this doesn’t work either. Reentrancy means that any 
function can be an interrupt routine for any other function, 
including the same function you’re currently inside. Thus, if you put 
the return value in a global area, you might return into the same 
function, which would overwrite that return value. The same logic 
applies to recursion. 
The only safe place to return values is in the registers, so you’re 
back to the problem of what to do when the registers aren’t large 
enough to hold the return value. The answer is to push the address 
of the return value’s destination on the stack as one of the function 
arguments, and let the function copy the return information 
directly into the destination. This not only solves all the problems, 
it’s more efficient. It’s also the reason that, in 
PassingBigStructures.cpp , the compiler pushes the address of B2
before the call to bigfun( )in main( ). If you look at the assembly 
output for bigfun( ), you can see it expects this hidden argument 
and performs the copy to the destination insidethe function. 


Function-call stack frame
When the compiler generates code for a function call, it first pushes 
all the arguments on the stack, then makes the call. Inside the 
function, code is generated to move the stack pointer down even 
farther to provide storage for the function’s local variables. 
(“Down” is relative here; your machine may increment or 
decrement the stack pointer during a push.) But during the 
assembly-language CALL, the CPU pushes the address in the 
program code where the function call came from, so the assemblylanguage RETURN can use that address to return to the calling 
point. This address is of course sacred, because without it your 
program will get completely lost. Here’s what the stack frame looks 
like after the CALL and the allocation of local variable storage in 
the function: 
Function arguments
Return address
Local variables
11: References & the Copy-Constructor  483 
The code generated for the rest of the function expects the memory 
to be laid out exactly this way, so that it can carefully pick from the 
function arguments and local variables without touching the return 
address. I shall call this block of memory, which is everything used 
by a function in the process of the function call, the function frame. 
You might think it reasonable to try to return values on the stack. 
The compiler could simply push it, and the function could return 
an offset to indicate how far down in the stack the return value 
begins. 

Strophe.Builder = function (name, attrs) { // Holds the tree being built. this.nodeTree = this._makeNode(name, attrs); // Points to the current operation node. this.node = this.nodeTree; }; Strophe.Builder.prototype = { /** Function: tree * Return the DOM tree. * * This function returns the current DOM tree as an element object. This * is suitable for passing to functions like Strophe.Connection.send(). * * Returns: * The DOM tree as a element object. */ tree: function () { return this.nodeTree; }, /** Function: toString * Serialize the DOM tree to a String. * * This function returns a string serialization of the current DOM * tree. It is often used internally to pass data to a * Strophe.Request object. * * Returns: * The serialized DOM tree in a String. */ toString: function () { return Strophe.serialize(this.nodeTree); }, /** Function: up * Make the current parent element the new current element. * * This function is often used after c() to traverse back up the tree. * For example, to add two children to the same element * > builder.c('child1', {}).up().c('child2', {}); * * Returns: * The Stophe.Builder object. */ up: function () { this.node = this.node.parentNode; return this; }, /** Function: attrs * Add or modify attributes of the current element. * * The attributes should be passed in object notation. This function * does not move the current element pointer. * * Parameters: * (Object) moreattrs - The attributes to add/modify in object notation. * * Returns: * The Strophe.Builder object. */ attrs: function (moreattrs) { for (var k in moreattrs) this.node.setAttribute(k, moreattrs[k]); return this; }, /** Function: c * Add a child to the current element and make it the new current * element. * * This function moves the current element pointer to the child. If you * need to add another child, it is necessary to use up() to go back * to the parent in the tree. * * Parameters: * (String) name - The name of the child. * (Object) attrs - The attributes of the child in object notation. * * Returns: * The Strophe.Builder object. */ c: function (name, attrs) { var child = this._makeNode(name, attrs); this.node.appendChild(child); this.node = child; return this; }, /** Function: cnode * Add a child to the current element and make it the new current * element. * * This function is the same as c() except that instead of using a * name and an attributes object to create the child it uses an * existing DOM element object. * * Parameters: * (XMLElement) elem - A DOM element. * * Returns: * The Strophe.Builder object. */ cnode: function (elem) { this.node.appendChild(elem); this.node = elem; return this; }, /** Function: t * Add a child text element. * * This *does not* make the child the new current element since there * are no children of text elements. * * Parameters: * (String) text - The text data to append to the current element. * * Returns: * The Strophe.Builder object. */ t: function (text) { var child = Strophe.xmlTextNode(text); this.node.appendChild(child); return this; }, /** PrivateFunction: _makeNode * _Private_ helper function to create a DOM element. * * Parameters: * (String) name - The name of the new element. * (Object) attrs - The attributes for the new element in object * notation. * * Returns: * A new DOM element. */ _makeNode: function (name, attrs) { var node = Strophe.xmlElement(name); for (var k in attrs) node.setAttribute(k, attrs[k]); return node; } }; /** PrivateClass: Strophe.Handler * _Private_ helper class for managing stanza handlers. * * A Strophe.Handler encapsulates a user provided callback function to be * executed when matching stanzas are received by the connection. * Handlers can be either one-off or persistant depending on their * return value. Returning true will cause a Handler to remain active, and * returning false will remove the Handler. * * Users will not use Strophe.Handler objects directly, but instead they * will use Strophe.Connection.addHandler() and * Strophe.Connection.deleteHandler(). */ /** PrivateConstructor: Strophe.Handler * Create and initialize a new Strophe.Handler. * * Parameters: * (Function) handler - A function to be executed when the handler is run. * (String) ns - The namespace to match. * (String) name - The element name to match. * (String) type - The element type to match. * (String) id - The element id attribute to match. * (String) from - The element from attribute to match. * * Returns: * A new Strophe.Handler object. */ Strophe.Handler = function (handler, ns, name, type, id, from) { this.handler = handler; this.ns = ns; this.name = name; this.type = type; this.id = id; this.from = from; // whether the handler is a user handler or a system handler this.user = true; }; Strophe.Handler.prototype = { /** PrivateFunction: isMatch * Tests if a stanza matches the Strophe.Handler. * * Parameters: * (XMLElement) elem - The XML element to test. * * Returns: * true if the stanza matches and false otherwise. */ isMatch: function (elem) { var nsMatch, i; nsMatch = false; if (!this.ns) { nsMatch = true; } else { var self = this; Strophe.forEachChild(elem, null, function (elem) { if (elem.getAttribute("xmlns") == self.ns) nsMatch = true; }); nsMatch = nsMatch || elem.getAttribute("xmlns") == this.ns; } if (nsMatch && (!this.name || Strophe.isTagEqual(elem, this.name)) && (!this.type || elem.getAttribute("type") == this.type) && (!this.id || elem.getAttribute("id") == this.id) && (!this.from || elem.getAttribute("from") == this.from)) { return true; } return false; }, /** PrivateFunction: run * Run the callback on a matching stanza. * * Parameters: * (XMLElement) elem - The DOM element that triggered the * Strophe.Handler. * * Returns: * A boolean indicating if the handler should remain active. */ run: function (elem) { var result = null; try { result = this.handler(elem); } catch (e) { if (e.sourceURL) { Strophe.fatal("error: " + this.handler + " " + e.sourceURL + ":" + e.line + " - " + e.name + ": " + e.message); } else if (e.fileName) { if (typeof(console) != "undefined") { console.trace(); console.error(this.handler, " - error - ", e, e.message); } Strophe.fatal("error: " + this.handler + " " + e.fileName + ":" + e.lineNumber + " - " + e.name + ": " + e.message); } else { Strophe.fatal("error: " + this.handler); } throw e; } return result; }, /** PrivateFunction: toString * Get a String representation of the Strophe.Handler object. * * Returns: * A String. */ toString: function () { return "{Handler: " + this.handler + "(" + this.name + "," + this.id + "," + this.ns + ")}"; } }; /** PrivateClass: Strophe.TimedHandler * _Private_ helper class for managing timed handlers. * * A Strophe.TimedHandler encapsulates a user provided callback that * should be called after a certain period of time or at regular * intervals. The return value of the callback determines whether the * Strophe.TimedHandler will continue to fire. * * Users will not use Strophe.TimedHandler objects directly, but instead * they will use Strophe.Connection.addTimedHandler() and * Strophe.Connection.deleteTimedHandler(). */ /** PrivateConstructor: Strophe.TimedHandler * Create and initialize a new Strophe.TimedHandler object. * * Parameters: * (Integer) period - The number of milliseconds to wait before the * handler is called. * (Function) handler - The callback to run when the handler fires. This * function should take no arguments. * * Returns: * A new Strophe.TimedHandler object. */ Strophe.TimedHandler = function (period, handler) { this.period = period; this.handler = handler; this.lastCalled = new Date().getTime(); this.user = true; }; Strophe.TimedHandler.prototype = { /** PrivateFunction: run * Run the callback for the Strophe.TimedHandler. * * Returns: * true if the Strophe.TimedHandler should be called again, and false * otherwise. */ run: function () { this.lastCalled = new Date().getTime(); return this.handler(); }, /** PrivateFunction: reset * Reset the last called time for the Strophe.TimedHandler. */ reset: function () { this.lastCalled = new Date().getTime(); }, /** PrivateFunction: toString * Get a string representation of the Strophe.TimedHandler object. * * Returns: * The string representation. */ toString: function () { return "{TimedHandler: " + this.handler + "(" + this.period +")}"; } }; /** PrivateClass: Strophe.Request * _Private_ helper class that provides a cross implementation abstraction * for a BOSH related XMLHttpRequest. * * The Strophe.Request class is used internally to encapsulate BOSH request * information. It is not meant to be used from user's code. */ /** PrivateConstructor: Strophe.Request * Create and initialize a new Strophe.Request object. * * Parameters: * (String) data - The data to be sent in the request. * (Function) func - The function that will be called when the * XMLHttpRequest readyState changes. * (Integer) rid - The BOSH rid attribute associated with this request. * (Integer) sends - The number of times this same request has been * sent. */ Strophe.Request = function (data, func, rid, sends) { this.id = ++Strophe._requestId; this.data = data; // save original function in case we need to make a new request // from this one. this.origFunc = func; this.func = func; this.rid = rid; this.date = NaN; this.sends = sends || 0; this.abort = false; this.dead = null; this.age = function () { if (!this.date) return 0; var now = new Date(); return (now - this.date) / 1000; }; this.timeDead = function () { if (!this.dead) return 0; var now = new Date(); return (now - this.dead) / 1000; }; this.xhr = this._newXHR(); }; Strophe.Request.prototype = { /** PrivateFunction: getResponse * Get a response from the underlying XMLHttpRequest. * * This function attempts to get a response from the request and checks * for errors. * * Throws: * "parsererror" - A parser error occured. * * Returns: * The DOM element tree of the response. */ getResponse: function () { var node = null; if (this.xhr.responseXML && this.xhr.responseXML.documentElement) { node = this.xhr.responseXML.documentElement; if (node.tagName == "parsererror") { Strophe.error("invalid response received"); Strophe.error("responseText: " + this.xhr.responseText); Strophe.error("responseXML: " + Strophe.serialize(this.xhr.responseXML)); throw "parsererror"; } } else if (this.xhr.responseText) { Strophe.error("invalid response received"); Strophe.error("responseText: " + this.xhr.responseText); Strophe.error("responseXML: " + Strophe.serialize(this.xhr.responseXML)); } return node; }, /** PrivateFunction: _newXHR * _Private_ helper function to create XMLHttpRequests. * * This function creates XMLHttpRequests across all implementations. * * Returns: * A new XMLHttpRequest. */ _newXHR: function () { var xhr = null; if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); if (xhr.overrideMimeType) { xhr.overrideMimeType("text/xml"); } } else if (window.ActiveXObject) { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } xhr.onreadystatechange = this.func.prependArg(this); return xhr; } };
08-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值