473,466 Members | 1,646 Online
Bytes | Software Development & Data Engineering Community
Create Post

Home Posts Topics Members FAQ

Accessing private member of a class through type-casting

Consider the code

class A {
private:
int a;
};

int main(void) {
A x;
int* ptr = (int*)&x;
*x = 10;
return 0;
}

I understand that I can not change the private attributes of a class,
but what does standard have to say about the code above?

Apr 7 '06 #1
31 3104

dragoncoder wrote:
Consider the code

class A {
private:
int a;
};

int main(void) {
A x;
int* ptr = (int*)&x;
*x = 10;
return 0;
}

I understand that I can not change the private attributes of a class,
but what does standard have to say about the code above?


That its behavior is undefined. It might work...it might not...it
might end the world as you know it...undefined.

Apr 7 '06 #2
dragoncoder posted:
Consider the code

class A {
private:
int a;
};

int main(void) {
A x;
You've create an object of the type "A". This object contains within it
an object of the type "int".

int* ptr = (int*)&x;
The expression "&x" is of the type "A*" (leaving out const). You have
used an old C-style cast to turn it into an "int*". This is illegal.

Even if your code was legal, you make the assumption that the address of
"x" is equal to the address of "x.a". I'm not sure if the Standard gives
any such guarantee. If the object was of a class which contained virtual
methods, your code would be even less reliable.
*x = 10;
The value of "x" is undefined, so your the behaviour of the above
statement is undefined.
return 0;
}

-Tomás
Apr 7 '06 #3

dragoncoder wrote:
int main(void) {
A x;
int* ptr = (int*)&x;
*x = 10;


Oh, I missed something here. *ptr = 10; would have been undefined. *x
= 10 doesn't compile as A doesn't have an operator*.

BTW, (int*)&x is totally legal. It is a C-style cast and as is the
case with all C-style casts it can do things unexpectedly. What this
resolves to is a reinterpret_cast<int*>(&x). This is of course legal
but has undefined results. A static_cast would be illegal and using
the C++ casting mechanism would have warned you about the undefined
nature of the cast...this is why C-Style casts are bad.

Apr 7 '06 #4
*x = 10;


I presume that should have been:

*ptr = 10;
-Tomás
Apr 7 '06 #5
Tomás wrote:
dragoncoder posted:
Consider the code

class A {
private:
int a;
};

int main(void) {
A x;
You've create an object of the type "A". This object contains within it
an object of the type "int".

int* ptr = (int*)&x;


The expression "&x" is of the type "A*" (leaving out const). You have
used an old C-style cast to turn it into an "int*". This is illegal.

Not quite illegal, merely undefined.
*x = 10;


The value of "x" is undefined, so your the behaviour of the above
statement is undefined.
return 0;
}

-Tomás

Apr 7 '06 #6
dragoncoder wrote:
Consider the code

class A {
private:
int a;
};

int main(void) {
A x;
int* ptr = (int*)&x;
*x = 10;
return 0;
}

I understand that I can not change the private attributes of a class,
but what does standard have to say about the code above?


If I turn my car to drive the wrong way up a one-way street, it obeys
me! Hah! Stupid car. Sometimes I will even get where I want to go.

Apr 7 '06 #7

Tomás wrote:
*x = 10;

I presume that should have been:

*ptr = 10;

Yes, i actually meant to write *ptr = 10; Apologies for the same.
-Tomás


Apr 7 '06 #8

Pete C wrote:
dragoncoder wrote:
Consider the code

class A {
private:
int a;
};

int main(void) {
A x;
int* ptr = (int*)&x;
*x = 10;
return 0;
}

I understand that I can not change the private attributes of a class,
but what does standard have to say about the code above?


If I turn my car to drive the wrong way up a one-way street, it obeys
me! Hah! Stupid car. Sometimes I will even get where I want to go.


But the result is pretty well defined even if there are a couple
alternative end scenarios. It is also, as apposed to the code in
question, quite illegal.

;)

Apr 7 '06 #9
If I correct the code so that *x = 10 becomes *ptr = 10, even then is
the behaviour undefined ?

I want to know which step I am doing wrong ? Is it illegal to assign
(int*)&x to ptr ? Or is it illegal to do *ptr = 10 ? I am confused. The
corrected is posted again.

class A {
private:
int a;
};

int main(void) {
A x;
int* ptr = (int*)&x;
*ptr = 10;
return 0;
}

Apr 7 '06 #10
dragoncoder wrote:
If I correct the code so that *x = 10 becomes *ptr = 10, even then is
the behaviour undefined ?
Yes.
I want to know which step I am doing wrong ? Is it illegal to assign
(int*)&x to ptr ? Or is it illegal to do *ptr = 10 ? I am confused.


It is illegal to use 'ptr' after obtaining it by reinterpret_cast'ing
it from '&x'. The Standard says that if two pointers are unrelated (and
they are), all you can do with the pointer is to cast it back and get
the same pointer value iff the alignment requirements are no less strict
for the destination ("temporary") pointer. Something like that, anyway.

V
--
Please remove capital 'A's when replying by e-mail
I do not respond to top-posted replies, please don't ask
Apr 7 '06 #11

Victor Bazarov wrote:
dragoncoder wrote:
If I correct the code so that *x = 10 becomes *ptr = 10, even then is
the behaviour undefined ?


Yes.
I want to know which step I am doing wrong ? Is it illegal to assign
(int*)&x to ptr ? Or is it illegal to do *ptr = 10 ? I am confused.


It is illegal to use 'ptr' after obtaining it by reinterpret_cast'ing
it from '&x'.


The result is undefined...it is not illegal to do so.

Apr 7 '06 #12

dragoncoder wrote:
If I correct the code so that *x = 10 becomes *ptr = 10, even then is
the behaviour undefined ?


It becomes undefined. *x = 10 is illegal and won't compile.

Apr 7 '06 #13
dragoncoder wrote:
int* ptr = (int*)&x;
*ptr = 10;


Let me try to explain the illegality.

(int*) in C is the same as reinterpret_cast<> in C++. C++ only defines the
result of such a cast if it casts a pointer to what the data really is.

Such typecasts make the compiler pretend that a class A is an int. This
would be legal:

A * p = (A *)(int *) &x; // both could use reinterpret_cast

Your code might do anything (including appear to work correctly) because you
don't know if the address of A::a is the same as the address of its A. So
your cast might not end up pointing to an int.

Your code will most likely appear to work, because a compiler has no reason
to put any other data in A except the int a. Because the behavior is
undefined, it might break if you change something about your compiler, or
class A. For example, if you add a virtual destructor, your code will most
likely fail.

--
Phlip
http://www.greencheese.org/ZeekLand <-- NOT a blog!!!
Apr 7 '06 #14
Phlip posted:
dragoncoder wrote:
int* ptr = (int*)&x; *ptr = 10;
Let me try to explain the illegality.

(int*) in C is the same as reinterpret_cast<> in C++.

Not really. "reinterpret_cast" can't cast pointers to functions if I recall
correctly, while C-style casts can.
C++ only defines
the result of such a cast if it casts a pointer to what the data really
is.

Such typecasts make the compiler pretend that a class A is an int. This
would be legal:

A * p = (A *)(int *) &x; // both could use reinterpret_cast

I don't think this should work. The two types, "A*" and "int*" are
unrelated. Yes, they are both pointers, but what they point to are totally
different. In three steps, here's what you're doing:

1: Start off with the expression "&x" which is of type "A*".
2: You then use a C-style cast to turn it into an "int*". The Standard
doesn't guarantee that an "int*" can reliably hold the address of an object
of type "A", so the value might get corrupted.
3: Now you use a C-style cast again to turn it into an "A*", but the value
may already have been corrupted.

It's quite like the following:

double k = 32.2342;

double f = (double) (int) k;

f will have the value of 32 rather than 32.2342.

Your code might do anything (including appear to work correctly)
because you don't know if the address of A::a is the same as the
address of its A. So your cast might not end up pointing to an int.

I believe this is true.
-Tomás
Apr 7 '06 #15

Phlip wrote:
dragoncoder wrote:
int* ptr = (int*)&x;
*ptr = 10;


Let me try to explain the illegality.

(int*) in C is the same as reinterpret_cast<> in C++. C++ only defines the
result of such a cast if it casts a pointer to what the data really is.

Such typecasts make the compiler pretend that a class A is an int. This
would be legal:

A * p = (A *)(int *) &x; // both could use reinterpret_cast

Your code might do anything (including appear to work correctly) because you
don't know if the address of A::a is the same as the address of its A. So
your cast might not end up pointing to an int.

Your code will most likely appear to work, because a compiler has no reason
to put any other data in A except the int a. Because the behavior is
undefined, it might break if you change something about your compiler, or
class A. For example, if you add a virtual destructor, your code will most
likely fail.


You are explaining undefined behavior. The word "illegal" never
appears in the standard that I know of and at least not in the
definitions section. I would take "illegal" to mean against the
standard, which would equate to ill-formed. Such a cast is not against
the standard. The standard explicitly defines the mapping of such cast
as implementation-defined and that "a pointer to an object can be
explicitly cast to a pointer to an object of a different type." This
means that the cast of A* to int* is _allowed_ by the standard and the
result is /implementation-defined/.

The use of the resulting pointer is as undefined as using any other
random pointer. As such, the standard imposes no requirements
whatsoever so the construct is still not dissallowed or "illegal" by
any definition of the word I know of.

Apr 7 '06 #16
Noah Roberts wrote:
This
means that the cast of A* to int* is allowed by the standard and the
result is implementation-defined.


I'm aware, in our culture, that "illegal" does not necessarily mean "you
shouldn't do it".

class A {
public:
A() { assert((int)this == (int)&a); }
private:
Â*Â*intÂ*a;
};

There. If that assertion would pass, then the cast is "legal". And note that
"legal" also does not appear in The Standard, yack yack yack, etc.

--
Phlip
http://www.greencheese.org/ZeekLand <-- NOT a blog!!!
Apr 7 '06 #17

Tomás wrote:
Phlip posted:
dragoncoder wrote:
int* ptr = (int*)&x; *ptr = 10;
Let me try to explain the illegality.

(int*) in C is the same as reinterpret_cast<> in C++.

Not really. "reinterpret_cast" can't cast pointers to functions if I recall
correctly, while C-style casts can.


See 5.2.10. P4 states you can convert a pointer to an int. P5 states
you can convert a function pointer to another function pointer.
C++ only defines
the result of such a cast if it casts a pointer to what the data really
is.

Such typecasts make the compiler pretend that a class A is an int. This
would be legal:

A * p = (A *)(int *) &x; // both could use reinterpret_cast

I don't think this should work. The two types, "A*" and "int*" are
unrelated. Yes, they are both pointers, but what they point to are totally
different.


The behavior of the above expression is actually well defined. Given
certain requirements the result is the same as &x. Failing to meet
those requirements has "unspecified" results.

In three steps, here's what you're doing:
1: Start off with the expression "&x" which is of type "A*".
2: You then use a C-style cast to turn it into an "int*". The Standard
doesn't guarantee that an "int*" can reliably hold the address of an object
of type "A", so the value might get corrupted.
3: Now you use a C-style cast again to turn it into an "A*", but the value
may already have been corrupted.

It's quite like the following:

double k = 32.2342;

double f = (double) (int) k;

f will have the value of 32 rather than 32.2342.


Actually, it is nothing like that. double and int have well defined
conversions. That particular C-style cast is the same as two
static_cast operations. The previous is two reinterpret_cast
operations. Totally different casts...again, this is the problem with
c-style casting...they look the same but are not.

Apr 7 '06 #18
Noah Roberts wrote:
double k = 32.2342;

double f = (double) (int) k;

f will have the value of 32 rather than 32.2342.


Actually, it is nothing like that.Â*Â*doubleÂ*andÂ*intÂ*haveÂ*wellÂ*defined
conversions


Let's try "well defined _lossy_ conversions".

I'm beginning to suspect (because you read The Standard for me), that
pointer conversions are well-defined as lossless.

--
Phlip
http://www.greencheese.org/ZeekLand <-- NOT a blog!!!
Apr 7 '06 #19

Phlip wrote:
Noah Roberts wrote:
This
means that the cast of A* to int* is allowed by the standard and the
result is implementation-defined.


I'm aware, in our culture, that "illegal" does not necessarily mean "you
shouldn't do it".


The word illegal has a well defined definition in the English language,
let us look to it:

1. Prohibited by law.
2. Prohibited by official rules: an illegal pass in football.
3. Unacceptable to or not performable by a computer: an illegal
operation.

#1 does not apply - law in this case being the standard.
#2 also similarly to #1 does not apply.

The standard _allows_ the cast so by elimination it is not prohibited
;)

#3 is the only possibilty however since the operation IS performable I
don't see how it applies.

The standard _allows_ the cast...all implementations therefore have to
allow it and be able to perform it. The standard does not specify how.

So, you can redefine words if you wish but then you may as well be
speaking in Greek...or Martian for that matter.

Apr 7 '06 #20
Noah Roberts wrote:
The word illegal has a well defined definition in the English language,
let us look to it:


You snipped the part of my post where I flamed you for what I knew you'd
reply.

Please calm down.

--
Phlip
http://www.greencheese.org/ZeekLand <-- NOT a blog!!!
Apr 7 '06 #21

Phlip wrote:
Noah Roberts wrote:
The word illegal has a well defined definition in the English language,
let us look to it:
You snipped the part of my post where I flamed you for what I knew you'd
reply.


What??!!

Please calm down.


Ummm...yeah...ok then...

Apr 7 '06 #22
it works!!

Apr 7 '06 #23
dragoncoder posted:
Consider the code

class A {
private:
int a;
};

int main(void) {
A x;
int* ptr = (int*)&x;
*x = 10;
return 0;
}

I understand that I can not change the private attributes of a class,
but what does standard have to say about the code above?

I got lost lower down in the thread so I'll start again here.

Someone was trying to do something akin to the following:

std::string object("Hello");

std::string* p = (std::string*)(int*) &object;
And they implied that the above is perfectly okay, such that the programmer
can go on to use "p" as follows:

cout << *p;
Firstly, let's break that code up and turn it into:

std::string object("Hello"); //Line 1

int* p_int = (int*) &object; //Line 2

std::string* p_str = (std::string*) p_int; //Line 3

cout << *p_str; //Line 4
On Line 2, we are casting an "std::string*" to an "int*" and storing the
result in a variable of type "int*". The Standard gives no guarantee
whatsoever that an "std::string*" can be cast or stored accurately as an
"int*". It is possible that, on a particular platform, that the cast may
work perfectly, but it is not guaranteed to work, and so is not 100%
portable.

On Line 3, we are casting an "int*" to an "std::string*" and storing the
result in a variable of type "std::string*". The first thing to note here is
that the Standard gives no guarantee whatsoever that the value in "p_int" is
valid -- it may be corrupt. Even if we cast it back to an "std::string*",
we've no guarantee that it points to the object called "object". It's quite
like going from "double" to "int" and then back to "double" -- you won't
have the original double's value.

Line 4 might print "Hello", then again, it might print the New Testament if
you've got the bible open in Adobe Acrobat at the same time.
Then someone posed the argument that you can cast a pointer to an "int" and
back to the original pointer type and that it will work perfectly. Here's
some sample code:

std::string* p = new std::string("Hello");

int num = (int)p;

std::string* k = (std::string*)num;

cout << *k;

delete p;
The Standard gives no guarantee whatsoever that the above code will work.
The Standard says that you can cast a pointer value to an integral type, and
then back to the original pointer type, but the final value will only be
legitimate if the integral type had enough bits to store the pointer value.
Nowhere in the Standard are we given any guarantee that any of the integral
types are large enough to hold any of the pointer values; so even if you use
an "unsigned long", you still can't be sure it will work. It's not 100%
portable, and so, it has no place in the realms of Standard C++.
-Tomás

Apr 7 '06 #24
Tomás wrote:
[..]
Then someone posed the argument that you can cast a pointer to an
"int" and back to the original pointer type and that it will work
perfectly.
Do you think you could actually find out who did pose that argument?
Not that it matters terribly.
Here's some sample code:

std::string* p = new std::string("Hello");

int num = (int)p;

std::string* k = (std::string*)num;

cout << *k;

delete p;
The Standard gives no guarantee whatsoever that the above code will
work. The Standard says that you can cast a pointer value to an
integral type, and then back to the original pointer type, but the
final value will only be legitimate if the integral type had enough
bits to store the pointer value. Nowhere in the Standard are we given
any guarantee that any of the integral types are large enough to hold
any of the pointer values; so even if you use an "unsigned long", you
still can't be sure it will work. It's not 100% portable, and so, it
has no place in the realms of Standard C++.


While the Standard does not specify which type is "large enough", or that
such type exists at all, all implemenations I've encountered do provide
such type. I agree with your assessment of the situation WRT Standard
C++ language. I only wanted to mention real-world cases here. Anyone
who might be faced with the necessity to store a pointer in a variable
of an integral type, shouldn't immediately dismiss the idea, but should
simply look for a type that might be able to accommodate them.

As an example, MS Visual C++ for Win64 has its 'unsigned long' only
half the size of an object pointer, for example. However, it does have
the __int64 type (implementation-specific) that can hold a value of
an object pointer.

V
--
Please remove capital 'A's when replying by e-mail
I do not respond to top-posted replies, please don't ask
Apr 7 '06 #25

Tomás wrote:
The first thing to note here is
that the Standard gives no guarantee whatsoever that the value in "p_int"is
valid -- it may be corrupt. Even if we cast it back to an "std::string*",
we've no guarantee that it points to the object called "object".


Actually, the standard has something to say about this.

"A pointer to an object may be explicitly converted to a pointer to an
object of a different type [65]. Except that converting an rvalue of
type "pointer to T1" to the type "pointer to T2" (where T1 and T2 are
object types and where the alignment requirements of T2 are no stricter
than those of T1) and back to its original type yields the original
pointer value, the result of such a pointer conversion is unspecified."

The term unspecified has a specific meaning different than undefined in
that the implementation is not free to call it an error. It can still
result in any useful or not useful pointer value. However, as you can
see above, unless the two objects have different alignment requirements
the operation is reversable.

footnote 65 states: "The types may have different cv-qualifiers,
subject to the overall restriction that a reinterpret_cast cannot cast
away constness."

Now, if such an operation had no place in std c++ then I suppose you
should tell the people that wrote the standard as they explicitly layed
out what such an operation's effects would be.

Now, you compare this operation to casting from double to int back to
double, but that has a totally different effect. I already explained
why in a different post.

Apr 7 '06 #26
Victor Bazarov wrote:
Tomás wrote:
[..]
Then someone posed the argument that you can cast a pointer to an
"int" and back to the original pointer type and that it will work
perfectly.


As an example, MS Visual C++ for Win64 has its 'unsigned long' only
half the size of an object pointer, for example. However, it does have
the __int64 type (implementation-specific) that can hold a value of
an object pointer.


There is int64_t and uint64_t which are at least C99.

Apr 7 '06 #27
Noah Roberts wrote:
footnote 65 states: "The types may have different cv-qualifiers,
subject to the overall restriction that a reinterpret_cast cannot cast
away constness."


....or volatility.

I thought it also cannot cast-in constness (or volatility).

--
Phlip
http://www.greencheese.org/ZeekLand <-- NOT a blog!!!
Apr 7 '06 #28

Phlip wrote:
Noah Roberts wrote:
footnote 65 states: "The types may have different cv-qualifiers,
subject to the overall restriction that a reinterpret_cast cannot cast
away constness."
...or volatility.


My quote is a direct quote...hence the marks.
I thought it also cannot cast-in constness (or volatility).


The standard says what it says. The standard only specifies that it
can't be cast away. Most things having to do with const are like
that...can become const but not not.

Apr 7 '06 #29
Now, if such an operation had no place in std c++ then I suppose you
should tell the people that wrote the standard as they explicitly layed
out what such an operation's effects would be.


I look at C++ programming from two viewpoints:

1) Writing implementation-independant code. Basically, when I want to
solve a problem by writing an algorithm, I will write Standard portable
code. I will refrain from any practises which could behave differently on
different plaforms. I wouldn't store the address of an std::string in an
int*.

2) Writing implementation-specific code. This is mostly when it comes to
writing the Graphical User Interface, or when I seek features that the
language and its Standard Libraries don't provide. When writing
implementation-specific code, you're free to use "tricks" which you have
a guarantee will work on the specific plaform. For instance, in Microsoft
Windows, it's common to pass a pointer to a function via an integral
function argument. Something like:

void SendMessage(long); //defined elsewhere

int p;

SendMessage( (long)*p );
But I won't do this in my portable code.
On this newsgroup, which is to do with Standard C++, I only ever mention
portable Standard practises, and I shun on other practises. If I was on a
MS Windows programming newsgroup, I'd conceed that there's nothing wrong
with storing an object's address in an integral type.
-Tomás
Apr 8 '06 #30

Tomás wrote:
If I was on a
MS Windows programming newsgroup, I'd conceed that there's nothing wrong
with storing an object's address in an integral type.


That is also necessary on other platforms. It is the whole reason the
standard guarantees that casting from one pointer type to the other and
back results in the same pointer value...given certain implementation
defined requirements that the programmer is expected to know. It is a
valid technique (necessary when dealing with any C based api), it is
topical, and it is in the standard.

Apr 8 '06 #31
Why would you want to? Reading the replies above, it's could be quite
unsafe to do that.

Reflection is what you need...

Apr 15 '06 #32

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

5
by: Sandeep | last post by:
Hi, In the following code, I wonder how a private member of the class is being accessed. The code compiles well in Visual Studio 6.0. class Sample { private: int x; public:
2
by: Steven T. Hatton | last post by:
I find the surprising. If I derive Rectangle from Point, I can access the members of Point inherited by Rectangle _IF_ they are actually members of a Rectangle. If I have a member of type Point...
4
by: Tom | last post by:
Let's say I've got a class defined in a header file. class foo { private: ... int bar; .... }; Now lets say I have a function that needs to access the private variable
4
by: ambar.shome | last post by:
Hi, Please take a look at the code snippet given below:#include<iostream> using namespace std; class A { public: virtual void test(){cout<<"Default implementation called"<<endl;}
8
by: dwok | last post by:
I have been wondering this for a while now. Suppose I have a class that contains some private member variables. How should I access the variables throughout the class? Should I use properties that...
23
by: Ben Voigt | last post by:
I have a POD type with a private destructor. There are a whole hierarchy of derived POD types, all meant to be freed using a public member function Destroy in the base class. I get warning C4624....
6
by: earthwormgaz | last post by:
Is the following legal? class Outer { class Inner { private: Inner() { } };
2
by: mickey22 | last post by:
Hi all, I have some data members and member functions that are declared as private in a class A. Now I want to use these data members and functions(which are private) in my new C++ source...
6
by: smoermeli | last post by:
#include <iostream> #include <vector> class Thing { private: int value; friend class Person; public: int getValue() { return value; } void...
2
by: agendum97 | last post by:
Is there a way of constructing a "class" in JavaScript to access a private member of a different scope of the same type? It is important that the variable remain private, and not accessible in...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...
0
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...
0
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing,...
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new...
0
by: conductexam | last post by:
I have .net C# application in which I am extracting data from word file and save it in database particularly. To store word all data as it is I am converting the whole word file firstly in HTML and...
0
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
0
by: 6302768590 | last post by:
Hai team i want code for transfer the data from one system to another through IP address by using C# our system has to for every 5mins then we have to update the data what the data is updated ...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.