When is "this" pointer initialized in C++? - c++

Hi I have a question about this pointer, when an object is constructed, when it is initialized? Which means, when can I use it? The virtual table is constructed in the constructor, is the same with this pointer?
For example, I have a code like this. The output is 8. Does it mean that before the constructor is entered, this pointer is already initialized?
class A{
public:
A() { cout<<sizeof(*this);}
int i;
int *p;
};
int main() {
A a;
}
If it is true, what else would happen before the constructor is entered ?
If it is not true, when is the this pointer initialized ?

The this pointer isn't a member of the object or class - it's an implicit parameter to the method that you call. As such it's passed in much like any other parameter - except that you don't directly ask for it.
In your example above, the constructor is a special method, which is in turn a special kind of function. When you construct the object, the compiler allocates memory for it (in this case on the stack, as a is a local variable in the main function. Then it automatically calls the constructor to initialise the object.
As part of calling the constructor, the implicit parameter this - a pointer to your object - is passed in as a parameter.
In a method with the following signature...
void MyMethod (const int* p) const;
there are actually two parameters, both pointers. There's the explicit parameter p and the implicit parameter this. The const at the end of the line specifies that this is a const pointer, much as the earlier one specifies that p is a const pointer. The need for that special syntax only exists because this is passed implicitly, so you can't specify const-ness in the normal way as with other parameters.
A "static" method doesn't have the implicit "this" parameter, and cannot directly access the object members either - there may not be a particular object associated with the call. It is basically a standard function rather than a method, except with access to private members (providing it can find an object to access).
As Steve Fallows points out, sizeof (this) is known at compile-time, because it is a pointer type and all pointers (*1) have the same sizeof value. The "8" you see implies you are compiling for a 64-bit platform. this is usable at this point - it points to valid memory, and all the members have completed their constructor calls. However, it isn't necessarily fully initialised - you are still in the constructor call after all.
EDIT
*1 - strictly, that may not be true - but the compiler knows what type of pointer it's dealing with here even though the value isn't known until runtime.

The this pointer is not stored. When the constructor is called for an object that occupies a specific memory location, that location is passed as a parameter to the constructor and other member functions.
If this would be stored inside the object, how to retrieve that pointer? Right, you would again need the this pointer :)

sizeof(*this) is known at compile time. So the cout statement reveals nothing about the initialization of this.
Given that the constructor can immediately begin accessing members of the object, clearly this is initialized before the constructor begins.
What else happens before the constructor? Well could be anything. I don't think the standard limits what a compiler could do. Maybe you should specify anything you're thinking might happen.

The virtual table is constructed in the constructor, is the same with this pointer?
The virtual table is NOT constructed in the constructor.
Typically, a single global v-table is shared by all instances of the same class, and each individual class has its own global v-table.The v-table is known at compile-time, and "constructed" at program load time.
The this pointer is "constructed" (I think "allocated" is a better term) at allocation time, that is, after the global new operator is called, and before the constructor is entered.
In cases where the object is stack-allocated instead of heap-allocated, global new is not called, but this is still available as a result of allocating stack-space, which is just before the constructor is entered.
The instance vptr is assigned after the object's memory is allocated, and just before the constructor is called.

Does it mean that before the constructor is entered, the this pointer is already initialized?
Yes, the value of the this pointer is known before the constructor is even called. This value is available via the this keyword inside constructors, constructor initialization lists, destructors, member methods. The this keyword behaves on the surface as a method variable (of pointer type) but is not one; it typically sits in a register (ecx on x86 platforms) and you typically won't be able to compile code like &this.
What else would happen before the constructor is entered
At least as far as the this pointer is concerned, the first thing that happens (unless using placement new) is the allocation of memory ultimately pointed to by this, be it on the stack (like in your example) or on the heap (using new.) At this point the this pointer is known. Then, either default constructors or explicitly specified constructors (via constructor initialization lists) are then called on the base classes (if any) and on your class non-POD member variables (if any). The class vtable pointer is also set before this point if your class contains virtual methods or destructor. Then, your class constructor body, if any, is invoked. (Constructor are called recursively, i.e. when a base class' constructor is called, the latter's base class constructors are called followed by non-POD member constructors, with the base class' vtable pointer being set, followed by the class' constructor body.)

The this pointer is the first argument to every call of the class, including the constructor.
When a class method is called, the address of the class is pushed onto the stack last (assuming cdecl calling convention here). This is read back into a register to use as the this pointer.
Constructors are in fact called as if they were ordinary member functions.
You cannot have a virtual constructor because the constructor is responsible for setting the vtable member.

As nobugz already pointed out, your example doesn't really mean much -- sizeof yields its results based on the type of the object you pass to it. It does not evaluate its operand at run-time.
That said, yes, this is initialized before entry to the ctor. Basically, the compiler allocates space for the object (on the stack if the object has automatic storage duration, or using ::operator new if it has dynamic storage duration). Upon entry to the ctor, the ctors for base classes (if any) have already run to completion. When your ctor is called, this gives the address of the memory that was allocated for the object.

this starts pointing to the current object and all members and base classes have been initialized before you enter the constructor body.
Therefore, you can hand out pointer to this in the initialization list, but the receiver should do nothing else other than storing it, because the pointed-at instance may not be fully constructed at the time.
#include <iostream>
class B;
class A
{
B* b_ptr;
public:
A(B* b);
};
class B
{
A a;
int i;
public:
B(): a(this), i(10) {}
void foo() const { std::cout << "My value is " << i << '\n'; }
};
A::A(B* b):
b_ptr(b) //Ok to store
{
b_ptr->foo(); //not OK to use, will access initialized member
}
int main()
{
B b;
}

Related

Initialization after deletion of constructor

My question is this: after deleting a constructor is there any way to initialize a class? For example
class A{
public:
A() = delete;
int a = 42;
int fun(){
a += 1;
return a;
}
};
Now it should not be possible to use this class. For example you cannot:
A* instance = (A*)malloc(sizeof(A));
instance->fun(); //segfault
and
A instance; //compile error undefined function
Assuming for some strange reason you actually wanted to use the class with the constructor deleted is there a way that you could do it? That is without doing something like overriding the constructor.
I know this is a strange question, but I am interested in knowing if someone has a (perhaps obscure) way of doing this. Thanks!
If the class is an aggregate you can create one using aggregate initialization, which does not call a constructor of A. For example:
A a = { 1 };
In C++11 your class was not an aggregate because of the presence of the = 42. So in C++11 your class is unusable.
But in C++14 this was changed, and = 42 does not prevent the class being an aggregate, so it is usable again.
The reason this is allowed is that calling malloc is not equivalent to calling new. malloc merely allocates memory and returns where it begins; new allocates and constructs an object. Just directly creating an A, as in A instance;, runs the constructor as well - so it expects it to be there.
As such, it is not an error as you never actually construct any A object. You allocate uninitialized memory, cast the pointer to A*, and then call it. This is valid because the language and the compiler don't care (or likely know) where you got that memory. All it knows is that at runtime, instance contains valid allocated memory; if that were not the case, it might segfault (it also might not, but would still probably be bad).
What happens when you actually run this is that fun() will access uninitialized memory as if it were an A; as such, int a will be something, but probably not 42.
It's not required that a pointer be to a constructable class. For example, you can cast an actual class pointer to one of its non-constructable interface classes and still access it. Effectively, the same methodology that allows that allows this.

Why I cannot call virtual function from a casted byte(char) array?

Having class A, for example,
class A{
public:
int x;
void Update(){
cout << " from a\n";
}
};
In order to instantiate an object of A without calling the constructor, it goes like:
A* a = (A*) new char[sizeof(A)];
a->x = 9;
cout << a->x; a->Update(); //"9 from a" is outputted
now In case Update is a virtual function
class A{
public:
int x;
virtual void Update(){
cout << " from a\n";
}
};
It throw an "Access violation" exception, why ?
When a class declares one or more virtual functions, the compiler implicitly adds a V-Table pointer to the class declaration, and thereby, to each object.
For every virtual-function-call, the compiler generates code which reads the function's address from the object's V-Table pointer, and then jumps to that address.
The V-Table pointer of an object is initialized only during runtime, when the constructor of the class is called and the object is created.
So when you create an object without explicitly calling the object's class-constructor, the V-Table pointer of that object is not initialized.
Then, when you call a virtual function of that object, the CPU attempts to read the function's address from the object's V-Table pointer, and a memory access violation occurs.
Calling a virtual function involves dereferencing the virtual function table pointer, stored within the object. It is dereferencing the place in memory where this value is supposed to be, but of course, it's uninitialized garbage, hence the error.
From the context of the question, it looks to me like you might be interested in Placement new. Placement new will allow you to re-use heap memory that has already been allocated, but will properly initialize the vtable pointer.
If you don't call the constructor of the object, then the virtual method table is not initialized. Therefore you can't expect calling a virtual function call to work. What are you trying to do in the first place >
Casting from an array of bytes into an object is only valid for POD types, that is, types that comply:
No user-defined destructor, constructor or copy operator.
Only public member variables.
No base classes.
No virtual functions.
No member variables of non-POD type, recursively.
That is, your first example is a POD, but your second one is not.
So your cast is valid for the first but not for the second. In the second case you will need to call placement new to construct a proper A object:
char *bytes = new char[sizeof(A)];
A *a= new (bytes) A;
And then, it is your responsibility to call the destructor when you are finished with it:
a->~A();
What are you expecting? Writing silly code results in silly results.
Anyway the objects need a virtual table - see What are you expecting? Writing silly code results in silly results. This table is not being initialised in the code supplied
See http://www.learncpp.com/cpp-tutorial/125-the-virtual-table/

Returning reference to data memeber in cpp

What I know, returning a reference to a local variable is the same as returning pointer to local variable and this causes memory leak in C++.
But does this apply to data members?
The code:
class MyClass
{
public:
std::string& getId();
private:
std::string id;
};
MyClass std::string& getId()
{
return id;
}
int main()
{
MyClass* c = new MyClass;
std::string brokenRef = c->getId();
// or may be std::string& brokenRef = c->getId();
delete c;
cout << brokenRef << endl; // <<< this should be a ref to unknown location, correct?
}
Thanks.
Yes, it applies. Even though your MyClass instance is not strictly local to main, but dynamically allocated and deallocated before the reference. Has the same effect, though.
The code as it stands is correct, because you copy the string while it is valid. The commented-out reference version is truly a broken reference.
In line
std::string brokenRef = c->getId();
You create a new instance of string and intialize it with a string referenced by reference returned by getId(). From this point on brokenRef lives completely independent life from MyClass object. Therefore brokenRef happily outlived MyClass object you destroyed.
You could have achieved desired affect by assigning reference to a reference variable:
std::string& brokenRef = c->getId();
In addition to this, I think you mixed terms memory leak and dangling pointer (dangling reference). Returning a pointer or a reference of a member does not cause memory leaks. But using them (dereferencing) after object is destroyed (so memory where members used to be stored is freed and they are becoming dangling) causes undefined behaviour and very likely crash.
It's okay in your example, because you assign to string, if you turn it into string& then it will be invalid as soon as you delete c.
(and it doesn't exactly cause memory leak).
as long as your MyClass is not deleted the reference is valid.
or if you would have declared it as a stack variable then as long as its in scope the members in the class instance are valid.
You will get some value but it will be some random value that as the object that contains it has been destroyed.
ideally I would delete and set that to null
so in your e.g.
c=null;
Object Oriented C++ has same object lifetime constraints as every OO based language
The life-cycle for an object begins when it is created, and ends whenit is destroyed. In a C++ class definition, a member function with thesame name as the class is a constructor. This is a function whichis called automatically whenever an instance of the class is created. Constructorsare typically used to initialize the data members of the object to theirdefault state, but may also be used to allocate resources (memory, files,etc.). For any class, a number of constructor functions may be declared,each taking different types of arguments, providing different ways of initializinginstances. A default constructor for a class is a constructor ofthat class that can be called without any arguments. A default constructorfor a class will be automatically generated if no constructor has beenexplicitly declared for that class. A copy constructor for a classis a constructor that can be called to copy an object of that class (ithas a single argument of the corresponding type). A copy constructor iscalled when, for example, an argument object is passed by value to a function,or when an object is initialized with the value of another object. A copyconstructor for a class will be automatically generated if no copy constructorhas been explicitly declared for that class. A member function with thesame name as the class with a leading tilde (~) is a destructor.This is a function that is called automatically when the object is deleted.The destructor is typically used to deallocate any memory allocated forthe object (and may also release any other resources acquired during construction).Constructors and destructors are not required in class definitions.
There are several ways to create objects in a C++ program. One is todefine a variable as being of a particular class, either as a global variableor as a local variable within a block. When the declaration is encounteredduring program execution, space is allocated for the object and the constructor,if any, for the object is called. Similarly, when an object variable goesout of scope, its destructor is called automatically. Another way to createan object is to declare a variable that is a pointer to the object classand call the C++ new operator, which will allocatespace for the object and call the constructor, if any, for the object.In this case, the pointer variable must be explicitly deallocated withthe delete operator. The constructor for theobject is executed when new is called, andthe destructor is executed when delete is called.An object can also be constructed by the explicit use of a constructorin an expression.
When a class is derived from another class, it inherits its parent class'constructor and destructor. Parent constructors are invoked before derivedconstructors. Destructors are invoked in the opposite direction, proceedingfrom the derived class upward through its parent chain.
More here http://www.objs.com/x3h7/cplus.htm

Is it undefined to initialize a class member in overloaded operator new?

Take a small example where, I am trying to find out if a variable is allocated on heap or not:
struct A
{
bool isOnHeap;
A () {} // not touching isOnHeap
~A () {}
void* operator new (size_t size)
{
A* p = (A*) malloc(size);
p->isOnHeap = true; // setting it to true
return p;
}
void operator delete (void *p) { free(p); }
};
It gives expected result in g++-4.5 (with warning for stack object). Is it ill defined
to do such operations ?
You can't initialize class members in an overloaded operator new because the object's lifetime hasn't started. You can only initialize members during the construction of the object.
You have no guarantee that the implementation won't wipe the memory between the time operator new returns and the time the object's construction starts or that during object construction members that are specified to have an indeterminate value by the standard (e.g. because they are POD and not explicitly initialized in the constructor like isOnHeap) aren't deliberately set to something by the implementation.
Note that A has a non-trivial constructor (it is user-declared), so its lifetime doesn't start when the storage for the object is allocated (ISO/IEC 14882:2003, 3.8 [basic.life] / 1) and the program has undefined behavior if it uses a pointer to the storage for the object to access a non-static data member (3.8 / 5). Even if A was a POD type, it's value after the completion of the new-expression would still be indeterminate rather than necessarily being related to the values in the bytes in the storage for the object before the new-expression was evaluated.
As Charles said, the object only comes to lifetime after it has been newed, so setting data within your implementation of new is rather dangerous.
Also, when your developers use tools like Lint, there's a big chance that it complains that the member isOnHeap is not initialized in the constructor. If then someone thinks "hey, Lint is right, let's initialize isOnHeap in the constructor of A", this will undermine the mechanism that you try to achieve.
There is a second case of which you probably didn't think. Suppose that someone writes this:
class MyClass
{
public:
...
private:
struct A m_a;
};
int main()
{
MyClass *myVariable = new MyClass();
}
Then your implementation of new will not be called. Nevertheless the instance of A is allocated on the heap (as part of the MyClass instance).
Can you explain why you want to know whether something has been allocated on the heap or not? Maybe there's another, more elegant solution to your problem.
Even when not considering the operator new itself (which is nonstandard and I would even say ugly, but knowing the exact details of some particular compiler it might be workable), there is another problem with this, which renders it useless anyway: You have no guarantee the value od isOnHeap will not be true when allocated on the stack. The stack is not initialized and any garbage from function invocations done before can be found there.

Class members that are objects - Pointers or not? C++

If I create a class MyClass and it has some private member say MyOtherClass, is it better to make MyOtherClass a pointer or not? What does it mean also to have it as not a pointer in terms of where it is stored in memory? Will the object be created when the class is created?
I noticed that the examples in QT usually declare class members as pointers when they are classes.
If I create a class MyClass and it has some private member say MyOtherClass, is it better to make MyOtherClass a pointer or not?
you should generally declare it as a value in your class. it will be local, there will be less chance for errors, fewer allocations -- ultimately fewer things that could go wrong, and the compiler can always know it is there at a specified offset so... it helps optimization and binary reduction at a few levels. there will be a few cases where you know you'll have to deal with pointer (i.e. polymorphic, shared, requires reallocation), it is typically best to use a pointer only when necessary - especially when it is private/encapsulated.
What does it mean also to have it as not a pointer in terms of where it is stored in memory?
its address will be close to (or equal to) this -- gcc (for example) has some advanced options to dump class data (sizes, vtables, offsets)
Will the object be created when the class is created?
yes - the size of MyClass will grow by sizeof(MyOtherClass), or more if the compiler realigns it (e.g. to its natural alignment)
Where is your member stored in memory?
Take a look at this example:
struct Foo { int m; };
struct A {
Foo foo;
};
struct B {
Foo *foo;
B() : foo(new Foo()) { } // ctor: allocate Foo on heap
~B() { delete foo; } // dtor: Don't forget this!
};
void bar() {
A a_stack; // a_stack is on stack
// a_stack.foo is on stack too
A* a_heap = new A(); // a_heap is on stack (it's a pointer)
// *a_heap (the pointee) is on heap
// a_heap->foo is on heap
B b_stack; // b_stack is on stack
// b_stack.foo is on stack
// *b_stack.foo is on heap
B* b_heap = new B(); // b_heap is on stack
// *b_heap is on heap
// b_heap->foo is on heap
// *(b_heap->foo is on heap
delete a_heap;
delete b_heap;
// B::~B() will delete b_heap->foo!
}
We define two classes A and B. A stores a public member foo of type Foo. B has a member foo of type pointer to Foo.
What's the situation for A:
If you create a variable a_stack of type A on the stack, then the object (obviously) and its members are on the stack too.
If you create a pointer to A like a_heap in the above example, just the pointer variable is on the stack; everything else (the object and it's members) are on the heap.
What does the situation look like in case of B:
you create B on the stack: then both the object and its member foo are on the stack, but the object that foo points to (the pointee) is on the heap. In short: b_stack.foo (the pointer) is on the stack, but *b_stack.foo the (pointee) is on the heap.
you create a pointer to B named b_heap: b_heap (the pointer) is on the stack, *b_heap (the pointee) is on the heap, as well as the member b_heap->foo and *b_heap->foo.
Will the object be automagically created?
In case of A: Yes, foo will automatically be created by calling the implicit default constructor of Foo. This will create an integer but will not intitialize it (it will have a random number)!
In case of B: If you omit our ctor and dtor then foo (the pointer) will also be created and initialized with a random number which means that it will point to a random location on the heap. But note, that the pointer exists! Note also, that the implicit default constructor won't allocate something for foo for you, you have to do this explicitly. That's why you usually need an explicit constructor and a accompanying destructor to allocate and delete the pointee of your member pointer. Don't forget about copy semantics: what happens to the pointee if your copy the object (via copy construction or assignment)?
What's the point of all of this?
There are several use cases of using a pointer to a member:
To point to an object you don't own. Let's say your class needs access to a huge data structure that is very costly to copy. Then you could just save a pointer to this data structure. Be aware that in this case creation and deletion of the data structure is out of the scope of your class. Someone other has to take care.
Increasing compilation time, since in your header file the pointee does not have to be defined.
A bit more advanced; When your class has a pointer to another class that stores all private members, the "Pimpl idiom": http://c2.com/cgi/wiki?PimplIdiom, take also a look at Sutter, H. (2000): Exceptional C++, p. 99--119
And some others, look at the other answers
Advice
Take extra care if your members are pointers and you own them. You have to write proper constructors, destructors and think about copy constructors and assignment operators. What happens to the pointee if you copy the object? Usually you will have to copy construct the pointee as well!
In C++, pointers are objects in their own right. They're not really tied to whatever they point to, and there's no special interaction between a pointer and its pointee (is that a word?)
If you create a pointer, you create a pointer and nothing else. You don't create the object that it might or might not point to. And when a pointer goes out of scope, the pointed-to object is unaffected. A pointer doesn't in any way affect the lifetime of whatever it points to.
So in general, you should not use pointers by default. If your class contains another object, that other object shouldn't be a pointer.
However, if your class knows about another object, then a pointer might be a good way to represent it (since multiple instances of your class can then point to the same instance, without taking ownership of it, and without controlling its lifetime)
The common wisdom in C++ is to avoid the use of (bare) pointers as much as possible. Especially bare pointers that point to dynamically allocated memory.
The reason is because pointers make it more difficult to write robust classes, especially when you also have to consider the possibility of exceptions being thrown.
I follow the following rule: if the member object lives and dies with the encapsulating object, do not use pointers. You will need a pointer if the member object has to outlive the encapsulating object for some reason. Depends on the task at hand.
Usually you use a pointer if the member object is given to you and not created by you. Then you usually don't have to destroy it either.
This question could be deliberated endlessly, but the basics are:
If MyOtherClass is not a pointer:
The creation and destruction of MyOtherClass is automatic, which can reduce bugs.
The memory used by MyOtherClass is local to the MyClassInstance, which could improve performance.
If MyOtherClass is a pointer:
The creation and destruction of MyOtherClass is your responsibility
MyOtherClass may be NULL, which could have meaning in your context and could save memory
Two instances of MyClass could share the same MyOtherClass
Some advantages of pointer member:
The child (MyOtherClass) object can have different lifetime than its parent (MyClass).
The object can possibly be shared between several MyClass (or other) objects.
When compiling the header file for MyClass, the compiler doesn't necessarily have to know the definition of MyOtherClass. You don't have to include its header, thus decreasing compile times.
Makes MyClass size smaller. This can be important for performance if your code does a lot of copying of MyClass objects. You can just copy the MyOtherClass pointer and implement some kind of reference counting system.
Advantages of having the member as an object:
You don't have to explicitely write code to create and destroy the object. It's easier and and less error-prone.
Makes memory management more efficient because only one block of memory needs to be allocated instead of two.
Implementing assignment operators, copy/move constructors etc is much simpler.
More intuitive
If you make the MyOtherClass object as member of your MyClass:
size of MyClass = size of MyClass + size of MyOtherClass
If you make the MyOtherClass object as pointer member of your MyClass:
size of MyClass = size of MyClass + size of any pointer on your system
You might want to keep MyOtherClass as a pointer member because it gives you the flexibility to point it to any other class that is derived from it. Basically helps you implement dynamice polymorphism.
It depends... :-)
If you use pointers to say a class A, you have to create the object of type A e.g. in the constructor of your class
m_pA = new A();
Moreover, don't forget to destroy the object in the destructor or you have a memory leak:
delete m_pA;
m_pA = NULL;
Instead, having an object of type A aggregated in your class is easier, you can't forget to destroy it, because this is done automatically at the end of lifetime of your object.
On the other hand, having a pointer has the following advantages:
If your object is allocated on the
stack and type A uses a lot of memory
this won't be allocated from the
stack but from the heap.
You can construct your A object later (e.g. in a method Create) or destroy it earlier (in method Close)
An advantage of the parent class maintaining the relation to a member object as a (std::auto_ptr) pointer to the member object is that you can forward declare the object rather than having to include the object's header file.
This decouples the classes at build time allowing to modify the member object's header class without causing all the clients of your parent class to be recompiled as well even though they probably do not access the member object's functions.
When you use an auto_ptr, you only need to take care of construction, which you could typically do in the initializer list. Destruction along with the parent object is guaranteed by the auto_ptr.
The simple thing to do is to declare your members as objects. This way, you do not have to care about copy construction, destruction and assignment. This is all taken care of automatically.
However, there are still some cases when you want pointers. After all, managed languages (like C# or Java) actually hold member objects by pointers.
The most obvious case is when the object to be kept is polymorphic. In Qt, as you pointed out, most objects belong to a huge hierarchy of polymorphic classes, and holding them by pointers is mandatory since you don't know at advance what size will the member object have.
Please beware of some common pitfalls in this case, especially when you deal with generic classes. Exception safety is a big concern:
struct Foo
{
Foo()
{
bar_ = new Bar();
baz_ = new Baz(); // If this line throw, bar_ is never reclaimed
// See copy constructor for a workaround
}
Foo(Foo const& x)
{
bar_ = x.bar_.clone();
try { baz_ = x.baz_.clone(); }
catch (...) { delete bar_; throw; }
}
// Copy and swap idiom is perfect for this.
// It yields exception safe operator= if the copy constructor
// is exception safe.
void swap(Foo& x) throw()
{ std::swap(bar_, x.bar_); std::swap(baz_, x.baz_); }
Foo& operator=(Foo x) { x.swap(*this); return *this; }
private:
Bar* bar_;
Baz* baz_;
};
As you see, it is quite cumbersome to have exception safe constructors in the presence of pointers. You should look at RAII and smart pointers (there are plenty of resources here and somewhere else on the web).