cast pointer to int

S

somenath

Hi All,

I was going through one tutorial about C language's pointer.

After reading the tutorial I got couple of questions regarding cast
of pointer to integer .
In the exercise there is one question as mentioned .


"What is the output of following program? Assume that stack grows
towards lower addresses. Also assume that variables are pushed on to
the stack in the order in which they are declared.
"
For all the questions, assume size of int is 4, size of short is 2 and
sizeof char is 1 byte.

#include<stdio.h>
int main(void)
{
int x, y ;
int *p1 = &x;
int *p2 = &y;
printf("%d\n", (int)p1-(int)p2);
return 0;
}

The output of the program is
4.

The explanation of the output is as given blow.

"All the local variables are stored in stack. So, x, y, p1 and p2,
all these variables will be part of the stack. Since, stack follows
"last in first out" principle, the first local variable is pushed into
the stack. i.e x is pushed first, followed by y, followed by p1 and
p2. So, &p2 and &p1 would differ by 4 bytes. Since p1 is pushed
earlier than p2, it will have higher memory address than p2. Since the
pointers are cast to integer, by subtracting them, they just behave
like normal numbers and hence output would be 4."

My questions are
1) if the size of int is 4 is it safe to assume that sizeof pointer
to int also will be 4 ?
2) Is it safe to cast pointer to integer and use them ?
3) And if the size of int and and size of pointer to int differ the
what will happen to extra bits ?

Please help me to understand these points.

Regards,
Somenath
 
W

Walter Roberson

somenath said:
1) if the size of int is 4 is it safe to assume that sizeof pointer
to int also will be 4 ?

No. There are real architectures that use 4 byte ints and 8 byte
pointers. Sun and SGI for example. It is one of the more common
setups for systems that have 64 bit address spaces.
2) Is it safe to cast pointer to integer and use them ?

Pointers can be bigger than int. C99 defines a type which is
an integral type large enough to hold a pointer -- but does not
promise that you will be able to perform arithmetic on that type
I believe.

The conversion from pointer to integer will not necessarily give
you any value that you can use for address arithmetic. It would
be completely valid, for example, for the address bytes to get
stored into the integer in a strange order. For example, the
address might get written into consequative bytes of the integer
but integers on that system might be "little-endian" where the
Most Significant Byte for arithmetic purposes was not first byte.

3) And if the size of int and and size of pointer to int differ the
what will happen to extra bits ?

The usual for signed arithmetic overflow -- which is to say,
it's up to the implementation what happens. If you stored to an
unsigned int instead of a signed int, the semantics would be well
defined (essentially it would discard bits at the beginning.)
 
J

Jack Klein

Hi All,

I was going through one tutorial about C language's pointer.

Apparently a very bad tutorial about pointers in C, one that goes into
all kinds of non-portable and implementation-defined details that are
completely unnecessary, and not defined by the C language itself.
After reading the tutorial I got couple of questions regarding cast
of pointer to integer .
In the exercise there is one question as mentioned .

This is an extremely bad question, and one that 99.9% of C programmers
will never need to know the answer to in order to write correct code.
"What is the output of following program? Assume that stack grows
towards lower addresses. Also assume that variables are pushed on to
the stack in the order in which they are declared.
"

Where does it say that you should actually assume a "stack", something
that is common on many platforms, but neither defined nor required by
C?
For all the questions, assume size of int is 4, size of short is 2 and
sizeof char is 1 byte.

I work with implementations where this is not true. One that I work
with quite frequently has the following sizes for integer types:

char 1 byte
short 1 byte
int 1 byte
long 2 bytes
long long 4 bytes

Another has char, short, int, and long all 1 byte. I haven't looked
to see if they have a C99 compiler version, if they do size of long
long will be 2 bytes.
#include<stdio.h>
int main(void)
{
int x, y ;
int *p1 = &x;
int *p2 = &y;
printf("%d\n", (int)p1-(int)p2);

There are platforms where the value of a pointer is too large to fit
into an int, so these casts product undefined behavior.
return 0;
}

The output of the program is
4.

The explanation of the output is as given blow.

"All the local variables are stored in stack. So, x, y, p1 and p2,

Absolutely untrue, on some platforms in all cases, on many platforms
in at least some cases.
all these variables will be part of the stack. Since, stack follows
"last in first out" principle, the first local variable is pushed into
the stack. i.e x is pushed first, followed by y, followed by p1 and
p2. So, &p2 and &p1 would differ by 4 bytes. Since p1 is pushed
earlier than p2, it will have higher memory address than p2. Since the
pointers are cast to integer, by subtracting them, they just behave
like normal numbers and hence output would be 4."

My questions are

Your questions should be to the person who wrote the tutorial. They
should include such topics as these:

1. Why are you wasting all this effort on non-portable nonsense?

2. Why not just explain the proper declaration, definition, and use
of pointers without all this unnecessary stuff?
1) if the size of int is 4 is it safe to assume that sizeof pointer
to int also will be 4 ?
No.

2) Is it safe to cast pointer to integer and use them ?

No, in fact there is no requirement in the C standard that there even
be an integer type wide enough to represent a pointer.
3) And if the size of int and and size of pointer to int differ the
what will happen to extra bits ?

Undefined behavior happens, which is not a problem because there is no
need to actually do this sort of stupid casting in real programs
written by programmers who know what they are doing.
Please help me to understand these points.

Better you should try to understand why this information is not
necessary and largely a waste of time.

--
Jack Klein
Home: http://JK-Technology.Com
FAQs for
comp.lang.c http://c-faq.com/
comp.lang.c++ http://www.parashift.com/c++-faq-lite/
alt.comp.lang.learn.c-c++
http://www.club.cc.cmu.edu/~ajo/docs/FAQ-acllc.html
 
C

cr88192

somenath said:
Hi All,

I was going through one tutorial about C language's pointer.

The explanation of the output is as given blow.

"All the local variables are stored in stack. So, x, y, p1 and p2,
all these variables will be part of the stack. Since, stack follows
"last in first out" principle, the first local variable is pushed into
the stack. i.e x is pushed first, followed by y, followed by p1 and
p2. So, &p2 and &p1 would differ by 4 bytes. Since p1 is pushed
earlier than p2, it will have higher memory address than p2. Since the
pointers are cast to integer, by subtracting them, they just behave
like normal numbers and hence output would be 4."

it is not safe to assume or rely on any of this.

it is really, up to the compiler both the sizes and ordering of the stack
arguments (or, technically, even if the stack is used at all...).

if these are the contents of the tutorial, then the author is relying
heavily on system-dependent assumptions...

My questions are
1) if the size of int is 4 is it safe to assume that sizeof pointer
to int also will be 4 ?

no, this is faulty reasoning.
the size of a type is largely unrelated to the size of the size of the
pointer to the type.

by this same reasoning we could ask:
it the sizeof(char) is 1 is, is sizeof(char *) also 1.
or, it the sizeof(double) is 8 is, is sizeof(double *) also 8.

both of these are presumably false on an architecture for which the original
assertion (that both are 4) is true.

2) Is it safe to cast pointer to integer and use them ?

no, for the reasoning that pointers can be a much bigger type than an
integer.

consider windows or linux code compiled for x86-64, int is 4 bytes, but
pointers are 8.
we can't use int in this case, and on windows, long may be 4, and on linux,
8, so we can't safely use long either.

for example, in my case, this ends up requiring using ifdefs and generating
different code in different cases.

now, 'if' we have it, there is 'intptr_t', but that does little to say what
type of integer it is, so that is another problem...

all this is, yay, fun...

3) And if the size of int and and size of pointer to int differ the
what will happen to extra bits ?

this is system dependent, but most often, the high bits will be cut off, or,
if by some reason pointers were smaller than int (don't know of any specific
examples, but this is possible), then the pointer could be either zero or
sign extended (here, assuming 2's complement, and other things).

so, really, this all depends on the arch specific.

Please help me to understand these points.

one can only be as specific as they are being specific.

a specific compiler on a specific arch (say, gcc 3.4 on x86), may do one
thing, but elsewhere (say, gcc 3.4 on x86-64, or MSVC on x86), something
very different.


so, one can ask about x86 and x86-64, and get more specific answers:
pointers are 4, or pointers are 8 bytes, ...

but, much beyond this, little is certain.


even beyond this, one doesn't know.

one could be using x86, but say, they are running on an AS/400, or they are
using a compiler with DSM support (Distributed Shared Memory), and may
suddenly find, for example, that:
sizeof(char *) is 16...

or, more so, the particular compiler may implement some specific features
(or run in a framework which uses these features), and allocate space for
local variables, on the heap...

the size between 2 locals could be a few bytes, or all the way across the
address space, one can't really say.


none of this can be known with much certainty, as the compiler writer, OS,
and arch, are allowed more than a little freedom. if an app does something
arch specific and breaks, it is generally viewed as the fault of the app
writer, and not the compiler or OS.

(and, those of us who are compiler writers, live in a very different
arena...).


for what it is worth, one can generalize, somethimes, but in general, it is
not safe to generalize.

for example, I am not saying that what a specific arch or compiler is,
itself random (after all, it is often the case that we are forced to rely on
this), but one needs to know which is which.

what is general, what is specific, what is theory, and what is practice, are
all important, and are all different...


especially on this group, where people will jump out and bite...
 
U

user923005

Hi All,

I was going through one tutorial about C language's pointer.

After reading the tutorial I got  couple of questions regarding  cast
of pointer to integer .
In the exercise there is one question as mentioned .

"What is the output of following program? Assume that stack grows
towards lower addresses. Also assume that variables are pushed on to
the stack in the order in which they are declared.
"
For all the questions, assume size of int is 4, size of short is 2 and
sizeof char is 1 byte.

#include<stdio.h>
int main(void)
{
    int x, y ;
    int  *p1 = &x;
    int *p2 = &y;
    printf("%d\n", (int)p1-(int)p2);
    return 0;

}

The output of the  program  is
4.

The explanation of the output is as given blow.

"All the local variables are stored in stack.  So, x, y, p1 and p2,
all these variables will be part of the stack.  Since, stack follows
"last in first out" principle, the first local variable is pushed into
the stack.  i.e x is pushed first, followed by y, followed by p1 and
p2.   So, &p2 and &p1 would differ by 4 bytes.  Since p1 is pushed
earlier than p2, it will have higher memory address than p2. Since the
pointers are cast to integer, by subtracting them, they just behave
like normal numbers and hence output would be 4."

My questions are
1)      if the size of int  is 4 is it safe to assume that sizeof pointer
to int also will be 4 ? No.

2)      Is it safe to cast pointer to integer and use them ?
It is implementation defined.
3)      And  if the size of int and and size of pointer to int differ the
what will happen to extra bits ?
Because int was used, It seems to me that it could certainly
overflow. I am not sure if undefined behaviour is to be expected
here, but it is definitely not reliable.
Please help me to understand these points.

Regards,
Somenath

On Itanium running OpenVMS:
Next Cmd: run bugs
-8
Next Cmd:

On Win32:

C:\tmp>type bugs.c
#include<stdio.h>
int main(void)
{
int x,
y;
int *p1 = &x;
int *p2 = &y;
printf("%d\n", (int) p1 - (int) p2);
return 0;
}

C:\tmp>bugs
-4


That tutorial is garbage (or at least that portion of it is).
 
C

CBFalconer

somenath said:
After reading the tutorial I got couple of questions regarding
cast of pointer to integer . In the exercise there is one
question as mentioned .

"What is the output of following program? Assume that stack grows
towards lower addresses. Also assume that variables are pushed on
to the stack in the order in which they are declared. "

For all the questions, assume size of int is 4, size of short is
2 and sizeof char is 1 byte.

#include<stdio.h>
int main(void) {
int x, y ;
int *p1 = &x;
int *p2 = &y;
printf("%d\n", (int)p1-(int)p2);
return 0;
}

The output of the program is 4.

The explanation of the output is as given blow.

and is invalid.

The output of the program is undetermined. The result of
subtracting two pointers is undetermined unless both pointers are
to or into the same object. x and y are different objects. This
is specified in the C standard.

Your 'explanation' only applies to your particular implementation,
as effected by the version of compiler used at that time, on that
particular machine.
 
S

somenath

Apparently a very bad tutorial about pointers in C, one that goes into
all kinds of non-portable and implementation-defined details that are
completely unnecessary, and not defined by the C language itself.


This is an extremely bad question, and one that 99.9% of C programmers
will never need to know the answer to in order to write correct code.


Where does it say that you should actually assume a "stack", something
that is common on many platforms, but neither defined nor required by
C?


I work with implementations where this is not true. One that I work
with quite frequently has the following sizes for integer types:

char 1 byte
short 1 byte
int 1 byte
long 2 bytes
long long 4 bytes

Another has char, short, int, and long all 1 byte. I haven't looked
to see if they have a C99 compiler version, if they do size of long
long will be 2 bytes.


There are platforms where the value of a pointer is too large to fit
into an int, so these casts product undefined behavior.





Absolutely untrue, on some platforms in all cases, on many platforms
in at least some cases.



Your questions should be to the person who wrote the tutorial. They
should include such topics as these:

1. Why are you wasting all this effort on non-portable nonsense?

2. Why not just explain the proper declaration, definition, and use
of pointers without all this unnecessary stuff?


No, in fact there is no requirement in the C standard that there even
be an integer type wide enough to represent a pointer.


Undefined behavior happens, which is not a problem because there is no
need to actually do this sort of stupid casting in real programs
written by programmers who know what they are doing.


Better you should try to understand why this information is not
necessary and largely a waste of time.

Many thanks for the advice, guidance . But I can not ignore this kind
of tutorial.
Let me explain my situation . I got job one of the big software
company in our country. In that we need to pass one C examination. For
that examination they provide tutorial and exercises .If I don't pass
I will not get good project assignment or may loose job

My feeling is that in our company no body bothers about portability.
If it works for Linux and intel platform its enough .May be that's the
reason they bother about so much system specific details. Really I am
not sure what is the cause of providing such kind of tutorial.
 
S

santosh

somenath wrote:

Many thanks for the advice, guidance . But I can not ignore this kind
of tutorial.
Let me explain my situation . I got job one of the big software
company in our country. In that we need to pass one C examination. For
that examination they provide tutorial and exercises .If I don't pass
I will not get good project assignment or may loose job

My feeling is that in our company no body bothers about portability.
If it works for Linux and intel platform its enough .May be that's the
reason they bother about so much system specific details. Really I am
not sure what is the cause of providing such kind of tutorial.

In this case why don't you try an assembly language tutorial for the
x86. You get to go into all these sorts of details and more with
assembly language. Also, knowledge of the platform's assembly will
enable you to read and understand the compiler's output. This group is
not really the right place for such information. Try comp.arch or
perhaps comp.lang.asm.x86.
 
M

Mark F. Haigh

somenath said:
1) if the size of int is 4 is it safe to assume that sizeof pointer
to int also will be 4 ?

No. There are real architectures that use 4 byte ints and 8 byte
pointers. Sun and SGI for example. [...]
<snip>

[OT]

Don't forget AMD64 / EM64T, aka x86-64, which are __very__ common
these days. Also, due to the way they're licensing their IP, MIPS64
(in n64 mode) is becoming very popular across a wide variety of
vendors even though SGI is mostly (or completely?) dead.

<snip>

Mark F. Haigh
(e-mail address removed)
 
R

Ravishankar S

Jack Klein said:
Apparently a very bad tutorial about pointers in C, one that goes into
all kinds of non-portable and implementation-defined details that are
completely unnecessary, and not defined by the C language itself.


This is an extremely bad question, and one that 99.9% of C programmers
will never need to know the answer to in order to write correct code.


Where does it say that you should actually assume a "stack", something
that is common on many platforms, but neither defined nor required by
C?


I work with implementations where this is not true. One that I work
with quite frequently has the following sizes for integer types:

char 1 byte
short 1 byte
int 1 byte
long 2 bytes
long long 4 bytes

Another has char, short, int, and long all 1 byte. I haven't looked
to see if they have a C99 compiler version, if they do size of long
long will be 2 bytes.


There are platforms where the value of a pointer is too large to fit
into an int, so these casts product undefined behavior.


Absolutely untrue, on some platforms in all cases, on many platforms
in at least some cases.


Your questions should be to the person who wrote the tutorial. They
should include such topics as these:

1. Why are you wasting all this effort on non-portable nonsense?

2. Why not just explain the proper declaration, definition, and use
of pointers without all this unnecessary stuff?


No, in fact there is no requirement in the C standard that there even
be an integer type wide enough to represent a pointer.


Undefined behavior happens, which is not a problem because there is no
need to actually do this sort of stupid casting in real programs
written by programmers who know what they are doing.


Better you should try to understand why this information is not
necessary and largely a waste of time.

Here in India atleast, this is standard fare for interviews.
Somehow someone who "knows" that local variable are stored on the "stack" is
considered very good on C. Indeed the person who also analyzes a program
based on
this type of "implementation" specific knowledge is better.

more philosophically: a simple truth may be more useful in some contexts
than the complete truth!
 
J

James Kuyper

Walter said:
Pointers can be bigger than int. C99 defines a type which is
an integral type large enough to hold a pointer -- but does not
promise that you will be able to perform arithmetic on that type
I believe.

C99 specifies that 'intptr_t' and 'uintptr_t' are typedefs for such a
type, but only if there actually is such a type. A conforming
implementation need not have such a type. Whether or not either of these
types actually exists can be checked at compile type by testing whether
or not <stdint.h> #defines INTPTR_MAX or UINTPTR_MAX, respectively. If
either of those macros have been #defined, the corresponding typedef can
be used to reversibly convert a pointer value.
 
J

James Kuyper

Jack said:
Where does it say that you should actually assume a "stack", something

In the question itself: "Assume that stack ...". This is very explicitly
a question about implementation-specific behavior. That makes it of
limited value, but this is not the reason the question is ill-defined.

The real reason why this question is a bad one is that it doesn't give
you the needed implementation-specific information about whether and how
the pointer to int conversion works.

....
I work with implementations where this is not true. One that I work

Fine - but the question does not say or even imply that these are always
true. In fact, by explicitly stating those assumption, it is implicitly
drawing the reader's attention to the fact that these are assumptions
that need to be stated in order for the question to be answerable, and
that they need not be true for all implementations of C.

....
Absolutely untrue, on some platforms in all cases, on many platforms
in at least some cases.

Yes, but it was explicitly stated as part of the question that this IS
true for the particular implementation in question.

....
Your questions should be to the person who wrote the tutorial. They
should include such topics as these:

1. Why are you wasting all this effort on non-portable nonsense?

Possibly because some people do need to know such non-portable things.
People who are interfacing C and assembly language, for instance. A
specialized need, to be sure, but nonetheless a real one. Of course,
this question, even if it's defects were corrected, would have no place
in a general tutorial on C.
 
A

Army1987

CBFalconer said:
somenath wrote:
[snip other non-portable assumptions]
and is invalid.

The output of the program is undetermined. The result of
subtracting two pointers is undetermined unless both pointers are
to or into the same object. x and y are different objects. This
is specified in the C standard.
But he's not subtracting pointers, he's just subtracting ints.
(int)p1 - (int)p2 simply is the difference between the operands, and,
provided it doesn't overflow, it doesn't cause UB. (But, of course, what
(int)p1 means is implementation defined, and is allowed to raise a signal.)
 
A

Army1987

cr88192 said:
it is really, up to the compiler both the sizes and ordering of the stack
arguments (or, technically, even if the stack is used at all...).

6.2.4:
4 An object whose identifier is declared with no linkage and without the storage-class
specifier static has automatic storage duration.
5 For such an object that does not have a variable length array type, its lifetime extends
from entry into the block with which it is associated until execution of that block ends in
any way.

Since there is no way to have partly overlapping scopes one of which is a
strict subset of the other, this mean that the last object which entered
its lifetime will be the first one which will leave it. Isn't this what
"stack" usually means?
(Ok, yeah, variables could still exist outside their lifetime, but we're
not allowed to refer to them in any way, so we should assume they don't.)
 
W

Walter Roberson

Mark F. Haigh said:
Also, due to the way they're licensing their IP, MIPS64
(in n64 mode) is becoming very popular across a wide variety of
vendors even though SGI is mostly (or completely?) dead.

[OT]
SGI sold off MIPS several years ago. But SGI isn't dead; it's
sales bookings are growing at a good clip.
 
K

Keith Thompson

Army1987 said:
6.2.4:
4 An object whose identifier is declared with no linkage and without
the storage-class specifier static has automatic storage duration.
5 For such an object that does not have a variable length array
type, its lifetime extends from entry into the block with which it
is associated until execution of that block ends in any way.

Since there is no way to have partly overlapping scopes one of which is a
strict subset of the other, this mean that the last object which entered
its lifetime will be the first one which will leave it. Isn't this what
"stack" usually means?
(Ok, yeah, variables could still exist outside their lifetime, but we're
not allowed to refer to them in any way, so we should assume they don't.)

But that has nothing to do with the relative addresses of the objects.
There's no requirement or implication that an object is allocated at
the top of the "stack" at the beginning of its lifetime.
 
K

Keith Thompson

Army1987 said:
6.2.4:
4 An object whose identifier is declared with no linkage and without
the storage-class specifier static has automatic storage duration.
5 For such an object that does not have a variable length array
type, its lifetime extends from entry into the block with which it
is associated until execution of that block ends in any way.

Since there is no way to have partly overlapping scopes one of which is a
strict subset of the other, this mean that the last object which entered
its lifetime will be the first one which will leave it. Isn't this what
"stack" usually means?
(Ok, yeah, variables could still exist outside their lifetime, but we're
not allowed to refer to them in any way, so we should assume they don't.)

But that has nothing to do with the relative addresses of the objects.
There's no requirement or implication that an object is allocated at
the top of the "stack" at the beginning of its lifetime.
 
S

SM Ryan

Hey, Rocky! Watch me pull a pointer out of my int!

# My questions are
# 1) if the size of int is 4 is it safe to assume that sizeof pointer
# to int also will be 4 ?

In general, no. If you're going to assume that, you should verify,
with something like
int main(...) {
...
if (sizeof(int)!=4) seppuku("sizeof(int)!=4");
if (sizeof(void*)!=4) seppuku("sizeof(void*)!=4");
...
}

# 2) Is it safe to cast pointer to integer and use them ?

Cast to intptr_t. This is an integer large enough to hold a pointer.
Whether it is the same as int is implementation dependent.
You should not assume any relation between sizeof(intptr_t)
and sizeof(void*).

# 3) And if the size of int and and size of pointer to int differ the
# what will happen to extra bits ?

Use intptr_t and you don't have to worry. The implementation
will stuff the pointer in and pull it out again.
 
C

cr88192

Army1987 said:
6.2.4:
4 An object whose identifier is declared with no linkage and without the
storage-class
specifier static has automatic storage duration.
5 For such an object that does not have a variable length array type, its
lifetime extends
from entry into the block with which it is associated until execution of
that block ends in
any way.

Since there is no way to have partly overlapping scopes one of which is a
strict subset of the other, this mean that the last object which entered
its lifetime will be the first one which will leave it. Isn't this what
"stack" usually means?
(Ok, yeah, variables could still exist outside their lifetime, but we're
not allowed to refer to them in any way, so we should assume they don't.)

wrong "stack" here.

I mean as per the implementation (usually, some linear area of memory
accessed relative to a sliding pointer), and not as per the abstraction (in
this case, you seem to be implying that the behavior of the call frame is in
itself "the stack").

that, or you are just trying to be contrary, who knows...


consider:
void foo(int a, int b)
{
int c;
int d;
long long e;

assert((&a)<(&b));
assert((&d)<(&a));
assert((&c)>(&d));
assert((&e)<(&d));
}

do you expect that the standard makes any guerantees that the above
assertions will not fail?...


what if, as it so happens, an implementation does not use the stack for
holding local variables?

internally the code is reorganized into something analogous to:

struct foo_args_5acyz { int a; int b; };
struct foo_locals_787gK { long long e; int c; int d; };

void foo(register struct foo_args_5acyz *args)
{
register struct foo_args_5acyz *locals;
locals=malloc(sizeof(struct foo_args_5acyz));

assert((&(args->a))<(&(args->b)));
assert((&(locals->d))<(&(args->a)));
assert((&(locals->c))>(&(locals->d)));
assert((&(locals->e))<(&(locals->d)));

free(locals);
free(args);
}

do you claim that such a possible implementation is disallowed by the
standard?...

I assert that such an implementation is infact allowed by the standard, and
will make no real guerantees about the ordering of the stack (or, in this
particular case, a stack may not exist or be used by the architecture, the
architecture instead relying almost purely on registers and dynamic memory
allocation).

 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,070
Latest member
BiogenixGummies

Latest Threads

Top